From 0f9d0b0bf1cd5fb58f47f22bd6d52a9fac31c530 Mon Sep 17 00:00:00 2001 From: Johannes Dahlström Date: Thu, 9 Jul 2015 14:51:30 +0300 Subject: Support frozen columns in Grid editor (#16727) Change-Id: Iff797c3bf90a6021099a3ed4082cfca3a6fb3540 --- WebContent/VAADIN/themes/base/grid/grid.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'WebContent') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 1653032703..31403428cd 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -199,6 +199,12 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co border-left: none; } } + + .#{$primaryStyleName}-editor-cells.frozen > div { + @include box-shadow(1px 0 2px rgba(0,0,0,.1)); + border-right: $v-grid-cell-vertical-border; + border-left: none; + } .#{$primaryStyleName}-row-stripe > td { background-color: $v-grid-row-stripe-background-color; @@ -342,6 +348,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co .#{$primaryStyleName}-editor-cells { position: relative; white-space: nowrap; + + &.frozen { + z-index: 2; + } > div { display: inline-block; -- cgit v1.2.3 From 9734bc5dfa5d919e3214dc17581d3da3ad1a3ebd Mon Sep 17 00:00:00 2001 From: elmot Date: Tue, 30 Jun 2015 17:40:30 +0300 Subject: Grid sidebar menu design changed. See design document and comments at trac ticket. (#18325) Change-Id: I6686d131f015cf0b7b9a6b43ce43284218d5dd63 --- WebContent/VAADIN/themes/base/grid/grid.scss | 10 ++++++---- .../VAADIN/themes/chameleon/components/components.scss | 2 ++ .../VAADIN/themes/chameleon/components/grid/grid.scss | 12 ++++++++++++ WebContent/VAADIN/themes/reindeer/grid/grid.scss | 6 ++++++ WebContent/VAADIN/themes/runo/grid/grid.scss | 15 +++++++-------- WebContent/VAADIN/themes/valo/components/_grid.scss | 3 --- client/src/com/vaadin/client/widgets/Grid.java | 1 - .../grid/basicfeatures/server/GridSidebarThemeTest.java | 12 ++++++------ 8 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 WebContent/VAADIN/themes/chameleon/components/grid/grid.scss (limited to 'WebContent') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 31403428cd..f587dfef4f 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -108,6 +108,7 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co outline: none; padding: 0 4px; text-align: right; + line-height: 1; &::-moz-focus-inner { border: 0; @@ -128,10 +129,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co &.open { .#{$primaryStyleName}-sidebar-button { width: 100%; - + &:after { - content: "\00d7"; - font-size: 16px; + content: "\f0c9"; + font-size: $v-grid-header-font-size; line-height: 1; } } @@ -142,11 +143,12 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co } .v-ie8 &.open .#{$primaryStyleName}-sidebar-button:after { + vertical-align: middle; + text-align: center; display: inline; } .#{$primaryStyleName}-sidebar-content { - border-top: $v-grid-border; padding: 4px 0; .gwt-MenuBar { diff --git a/WebContent/VAADIN/themes/chameleon/components/components.scss b/WebContent/VAADIN/themes/chameleon/components/components.scss index 9c8a56b33d..578ea23bf3 100644 --- a/WebContent/VAADIN/themes/chameleon/components/components.scss +++ b/WebContent/VAADIN/themes/chameleon/components/components.scss @@ -1,6 +1,7 @@ @import "accordion/accordion.scss"; @import "button/button.scss"; @import "colorpicker/colorpicker.scss"; +@import "grid/grid.scss"; @import "label/label.scss"; @import "menubar/menubar.scss"; @import "notification/notification.scss"; @@ -24,6 +25,7 @@ @include chameleon-accordion; @include chameleon-button; @include chameleon-colorpicker; + @include chameleon-grid; @include chameleon-label; @include chameleon-menubar; @include chameleon-notification; diff --git a/WebContent/VAADIN/themes/chameleon/components/grid/grid.scss b/WebContent/VAADIN/themes/chameleon/components/grid/grid.scss new file mode 100644 index 0000000000..5007ad6619 --- /dev/null +++ b/WebContent/VAADIN/themes/chameleon/components/grid/grid.scss @@ -0,0 +1,12 @@ +@mixin chameleon-grid($primaryStyleName: v-grid) { + + // Sidebar + .#{$primaryStyleName}-sidebar.v-contextmenu { + + .v-on:before, .v-off:before { + content: none; + font-size: 0; + margin-right: 0; + } + } +} diff --git a/WebContent/VAADIN/themes/reindeer/grid/grid.scss b/WebContent/VAADIN/themes/reindeer/grid/grid.scss index 7ae0f402aa..cb5e9d454e 100644 --- a/WebContent/VAADIN/themes/reindeer/grid/grid.scss +++ b/WebContent/VAADIN/themes/reindeer/grid/grid.scss @@ -40,6 +40,12 @@ .#{$primaryStyleName}-sidebar-content { background-color: #f8f8f9; } + + .v-on:before, .v-off:before { + content: none; + font-size: 0; + margin-right: 0; + } } // Sort indicators diff --git a/WebContent/VAADIN/themes/runo/grid/grid.scss b/WebContent/VAADIN/themes/runo/grid/grid.scss index aca9821c53..1f049c5fb0 100644 --- a/WebContent/VAADIN/themes/runo/grid/grid.scss +++ b/WebContent/VAADIN/themes/runo/grid/grid.scss @@ -30,14 +30,7 @@ // Sidebar .#{$primaryStyleName}-sidebar.v-contextmenu { - &.open { - .#{$primaryStyleName}-sidebar-button { - &:after { - font-size: 22px; - } - } - } - + .#{$primaryStyleName}-sidebar-content { background-color: transparent; @@ -45,6 +38,12 @@ border: none; } } + + .v-on:before, .v-off:before { + content: none; + font-size: 0; + margin-right: 0; + } } // Sort indicators diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index 27d421b9f2..d00ddf30a4 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -203,9 +203,6 @@ $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; // Sidebar .#{$primary-stylename}-sidebar.v-contextmenu { &.open { - .#{$primary-stylename}-sidebar-button:after { - font-size: 20px; - } .#{$primary-stylename}-sidebar-content { margin: 0 0 2px; diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 232d67c1d4..377943ed61 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -3248,7 +3248,6 @@ public class Grid extends ResizeComposite implements clickOutsideToCloseHandlerRegistration = Event .addNativePreviewHandler(clickOutsideToCloseHandler); } - openCloseButton.setHeight(""); } /** diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java index 0e5dd32989..238b470feb 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java @@ -45,15 +45,15 @@ public class GridSidebarThemeTest extends GridBasicFeaturesTest { private void runTestSequence(String theme) throws IOException { openTestURL("theme=" + theme); - compareScreen(theme + "_SidebarClosed"); + compareScreen(theme + "-SidebarClosed"); getSidebarOpenButton().click(); - compareScreen(theme + "_SidebarOpen"); + compareScreen(theme + "-SidebarOpen"); new Actions(getDriver()).moveToElement(getColumnHidingToggle(2), 5, 5) .perform(); - compareScreen(theme + "_OnMouseOverNotHiddenToggle"); + compareScreen(theme + "-OnMouseOverNotHiddenToggle"); getColumnHidingToggle(2).click(); getColumnHidingToggle(3).click(); @@ -63,17 +63,17 @@ public class GridSidebarThemeTest extends GridBasicFeaturesTest { .perform(); ; - compareScreen(theme + "_TogglesTriggered"); + compareScreen(theme + "-TogglesTriggered"); new Actions(getDriver()).moveToElement(getColumnHidingToggle(2)) .perform(); ; - compareScreen(theme + "_OnMouseOverHiddenToggle"); + compareScreen(theme + "-OnMouseOverHiddenToggle"); getSidebarOpenButton().click(); - compareScreen(theme + "_SidebarClosed2"); + compareScreen(theme + "-SidebarClosed2"); } @Override -- cgit v1.2.3 From 554bdab01e987b09a78b23d049c0f9f9b2a2ec72 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 3 Jul 2015 15:52:06 +0300 Subject: Keep non-editable & selection columns visible in editor Change-Id: Ib3090d43e97667707590a2bca5f4641b72d7cd7f --- WebContent/VAADIN/themes/base/grid/grid.scss | 4 ++ .../VAADIN/themes/valo/components/_grid.scss | 4 ++ client/src/com/vaadin/client/widgets/Grid.java | 51 ++++++++++++++++++++++ server/src/com/vaadin/ui/Grid.java | 3 ++ .../basicfeatures/client/GridEditorClientTest.java | 2 +- .../grid/basicfeatures/server/GridEditorTest.java | 15 ++++--- 6 files changed, 72 insertions(+), 7 deletions(-) (limited to 'WebContent') diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index f587dfef4f..6b3b017070 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -396,6 +396,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co min-width: 100%; max-width: 100%; } + + &.not-editable.#{$primaryStyleName}-cell { + float: none; + } } .error::before { diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index d00ddf30a4..11134262eb 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -119,6 +119,10 @@ $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; vertical-align: middle; } + &.not-editable.#{$primary-stylename}-cell { + float: none; + } + .error::before { border-top: round($v-unit-size / 4) solid $v-error-indicator-color; border-right: round($v-unit-size / 4) solid transparent; diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 18407dccc9..a17c59c795 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -80,6 +80,7 @@ import com.vaadin.client.Focusable; import com.vaadin.client.WidgetUtil; import com.vaadin.client.data.DataChangeHandler; import com.vaadin.client.data.DataSource; +import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.renderers.ComplexRenderer; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.renderers.WidgetRenderer; @@ -1265,6 +1266,7 @@ public class Grid extends ResizeComposite implements private double originalTop; /** Original scroll position of grid when editor was opened */ private double originalScrollTop; + private RowHandle pinnedRowHandle; public Editor() { saveButton = new Button(); @@ -1626,6 +1628,50 @@ public class Grid extends ResizeComposite implements } } else { cell.addClassName(NOT_EDITABLE_CLASS_NAME); + cell.addClassName(tr.getCells().getItem(i).getClassName()); + // If the focused stylename is present it should not be + // inherited by the editor cell as it is not useful in the + // editor and would look broken without additional style + // rules. This is a bit of a hack. + cell.removeClassName(grid.cellFocusStyleName); + + if (column == grid.selectionColumn) { + // Duplicate selection column CheckBox + + pinnedRowHandle = grid.getDataSource().getHandle( + grid.getDataSource().getRow(rowIndex)); + pinnedRowHandle.pin(); + + // We need to duplicate the selection CheckBox for the + // editor overlay since the original one is hidden by + // the overlay + final CheckBox checkBox = GWT.create(CheckBox.class); + checkBox.setValue(grid.isSelected(pinnedRowHandle + .getRow())); + checkBox.sinkEvents(Event.ONCLICK); + + checkBox.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + T row = pinnedRowHandle.getRow(); + if (grid.isSelected(row)) { + grid.deselect(row); + } else { + grid.select(row); + } + } + }); + attachWidget(checkBox, cell); + columnToWidget.put(column, checkBox); + + // Only enable CheckBox in non-buffered mode + checkBox.setEnabled(!isBuffered()); + + } else if (!(column.getRenderer() instanceof WidgetRenderer)) { + // Copy non-widget content directly + cell.setInnerHTML(tr.getCells().getItem(i) + .getInnerHTML()); + } } } @@ -1697,6 +1743,11 @@ public class Grid extends ResizeComposite implements return; } + if (pinnedRowHandle != null) { + pinnedRowHandle.unpin(); + pinnedRowHandle = null; + } + for (Widget w : columnToWidget.values()) { setParent(w, null); } diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index ab4236fdf0..eabd3e00c7 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -6150,6 +6150,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * Opens the editor interface for the provided item. Scrolls the Grid to * bring the item to view if it is not already visible. * + * Note that any cell content rendered by a WidgetRenderer will not be + * visible in the editor row. + * * @param itemId * the id of the item to edit * @throws IllegalStateException diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java index 43fe29bc02..58d6559bfb 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java @@ -143,7 +143,7 @@ public class GridEditorClientTest extends GridBasicClientFeaturesTest { List selectorDivs = editorCells.findElements(By .cssSelector("div")); - assertTrue("selector column cell should've been empty", selectorDivs + assertFalse("selector column cell should've had contents", selectorDivs .get(0).getAttribute("innerHTML").isEmpty()); assertFalse("normal column cell shoul've had contents", selectorDivs .get(1).getAttribute("innerHTML").isEmpty()); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java index f7592ce922..0a77d690a4 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java @@ -197,13 +197,16 @@ public abstract class GridEditorTest extends GridBasicFeaturesTest { GridEditorElement editor = getGridElement().getEditor(); assertFalse("Uneditable column should not have an editor widget", editor.isEditable(3)); - assertEquals( - "Not editable cell did not contain correct classname", - "not-editable", - editor.findElements(By.className("v-grid-editor-cells")).get(1) - .findElements(By.xpath("./div")).get(3) - .getAttribute("class")); + String classNames = editor + .findElements(By.className("v-grid-editor-cells")).get(1) + .findElements(By.xpath("./div")).get(3).getAttribute("class"); + + assertTrue("Noneditable cell should contain not-editable classname", + classNames.contains("not-editable")); + + assertTrue("Noneditable cell should contain v-grid-cell classname", + classNames.contains("v-grid-cell")); } @Test -- cgit v1.2.3 From 2be70f04484c793fff15b21e661c5c56df443775 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 15 Jan 2015 21:59:00 +0200 Subject: Decode filename before finding resource (#15407) Change-Id: Iede2deff19058ee65bfe06ee8abb918218a57924 --- .../resource with special $chars@.txt | 1 + .../VAADIN/themes/tests-tickets/ordinary.txt | 1 + .../themes/tests-tickets/percentagein%20name.txt | 1 + server/src/com/vaadin/server/VaadinServlet.java | 20 +++++---- .../resources/SpecialCharsInThemeResources.java | 52 ++++++++++++++++++++++ 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 WebContent/VAADIN/themes/tests-tickets/folder with space/resource with special $chars@.txt create mode 100644 WebContent/VAADIN/themes/tests-tickets/ordinary.txt create mode 100644 WebContent/VAADIN/themes/tests-tickets/percentagein%20name.txt create mode 100644 uitest/src/com/vaadin/tests/resources/SpecialCharsInThemeResources.java (limited to 'WebContent') diff --git a/WebContent/VAADIN/themes/tests-tickets/folder with space/resource with special $chars@.txt b/WebContent/VAADIN/themes/tests-tickets/folder with space/resource with special $chars@.txt new file mode 100644 index 0000000000..dff31dd51f --- /dev/null +++ b/WebContent/VAADIN/themes/tests-tickets/folder with space/resource with special $chars@.txt @@ -0,0 +1 @@ +Just ordinary contents here \ No newline at end of file diff --git a/WebContent/VAADIN/themes/tests-tickets/ordinary.txt b/WebContent/VAADIN/themes/tests-tickets/ordinary.txt new file mode 100644 index 0000000000..dff31dd51f --- /dev/null +++ b/WebContent/VAADIN/themes/tests-tickets/ordinary.txt @@ -0,0 +1 @@ +Just ordinary contents here \ No newline at end of file diff --git a/WebContent/VAADIN/themes/tests-tickets/percentagein%20name.txt b/WebContent/VAADIN/themes/tests-tickets/percentagein%20name.txt new file mode 100644 index 0000000000..dff31dd51f --- /dev/null +++ b/WebContent/VAADIN/themes/tests-tickets/percentagein%20name.txt @@ -0,0 +1 @@ +Just ordinary contents here \ No newline at end of file diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index 7aada2402d..61df02feaa 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -28,6 +28,7 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -694,17 +695,20 @@ public class VaadinServlet extends HttpServlet implements Constants { return false; } + String decodedRequestURI = URLDecoder.decode(request.getRequestURI(), + "UTF-8"); if ((request.getContextPath() != null) - && (request.getRequestURI().startsWith("/VAADIN/"))) { - serveStaticResourcesInVAADIN(request.getRequestURI(), request, - response); + && (decodedRequestURI.startsWith("/VAADIN/"))) { + serveStaticResourcesInVAADIN(decodedRequestURI, request, response); return true; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/VAADIN/")) { + } + + String decodedContextPath = URLDecoder.decode(request.getContextPath(), + "UTF-8"); + if (decodedRequestURI.startsWith(decodedContextPath + "/VAADIN/")) { serveStaticResourcesInVAADIN( - request.getRequestURI().substring( - request.getContextPath().length()), request, - response); + decodedRequestURI.substring(decodedContextPath.length()), + request, response); return true; } diff --git a/uitest/src/com/vaadin/tests/resources/SpecialCharsInThemeResources.java b/uitest/src/com/vaadin/tests/resources/SpecialCharsInThemeResources.java new file mode 100644 index 0000000000..e584ec73cc --- /dev/null +++ b/uitest/src/com/vaadin/tests/resources/SpecialCharsInThemeResources.java @@ -0,0 +1,52 @@ +/* + * 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.resources; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class SpecialCharsInThemeResources extends SingleBrowserTest { + + @Test + public void loadThemeResource() { + loadResource("/VAADIN/themes/tests-tickets/ordinary.txt"); + checkSource(); + } + + @Test + public void loadThemeResourceWithPercentage() { + loadResource("/VAADIN/themes/tests-tickets/percentagein%2520name.txt"); + checkSource(); + } + + @Test + public void loadThemeResourceWithSpecialChars() { + loadResource("/VAADIN/themes/tests-tickets/folder%20with%20space/resource%20with%20special%20$chars@.txt"); + checkSource(); + } + + private void loadResource(String path) { + getDriver().get(getBaseURL() + path); + } + + private void checkSource() { + String source = getDriver().getPageSource(); + Assert.assertTrue("Incorrect contents (was: " + source + ")", + source.contains("Just ordinary contents here")); + } +} -- cgit v1.2.3 From 38e7c12bac61fb60fc4916e36b979cb3667db8e1 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Thu, 13 Aug 2015 08:27:58 +0300 Subject: Make Valo work with sass-lang compiler again (#18438) Change-Id: I0b49dc9a2d5a620244829739ccef644e3b7572ca --- WebContent/VAADIN/themes/valo/components/_grid.scss | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'WebContent') diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index d00ddf30a4..dffd5fbb65 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -21,6 +21,11 @@ $v-grid-details-marker-width: first-number($v-grid-border) * 2 !default; $v-grid-details-marker-color: $v-selection-color !default; $v-grid-details-border-top: valo-border($color: $v-grid-border-color-source, $strength: 0.3) !default; $v-grid-details-border-top-stripe: valo-border($color: $v-grid-row-stripe-background-color, $strength: 0.3) !default; + +$v-grid-border-size: 1px !default; +$v-grid-border: $v-grid-border-size solid #ddd !default; +$v-grid-cell-vertical-border: $v-grid-border !default; +$v-grid-cell-horizontal-border: $v-grid-cell-vertical-border !default; $v-grid-details-border-bottom: $v-grid-cell-horizontal-border !default; $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; -- cgit v1.2.3 From 6322134bddd505d9cdf44f8554bb6d337dabe88a Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Wed, 19 Aug 2015 23:55:16 +0300 Subject: Update to Bourbon 3.2.4 (#15484) Change-Id: I052702fc9b5b714c0d748973f3146f0718298a97 --- .../VAADIN/themes/valo/util/bourbon/_bourbon.scss | 16 +- .../themes/valo/util/bourbon/addons/_button.scss | 183 ++++++++++++++++----- .../util/bourbon/addons/_directional-values.scss | 13 +- .../util/bourbon/addons/_html5-input-types.scss | 6 +- .../themes/valo/util/bourbon/addons/_position.scss | 8 +- .../themes/valo/util/bourbon/addons/_rem.scss | 33 ---- .../valo/util/bourbon/addons/_retina-image.scss | 2 +- .../util/bourbon/addons/_timing-functions.scss | 2 +- .../themes/valo/util/bourbon/addons/_triangle.scss | 67 ++++++-- .../valo/util/bourbon/addons/_word-wrap.scss | 8 + .../themes/valo/util/bourbon/css3/_columns.scss | 2 +- .../themes/valo/util/bourbon/css3/_filter.scss | 5 + .../themes/valo/util/bourbon/css3/_flex-box.scss | 28 ++-- .../themes/valo/util/bourbon/css3/_font-face.scss | 2 +- .../util/bourbon/css3/_font-feature-settings.scss | 10 ++ .../valo/util/bourbon/css3/_image-rendering.scss | 1 + .../themes/valo/util/bourbon/css3/_keyframes.scss | 15 +- .../valo/util/bourbon/css3/_placeholder.scss | 27 +-- .../themes/valo/util/bourbon/css3/_transition.scss | 51 +++++- .../util/bourbon/functions/_color-lightness.scss | 13 ++ .../util/bourbon/functions/_modular-scale.scss | 2 +- .../valo/util/bourbon/functions/_px-to-rem.scss | 15 ++ .../functions/_transition-property-name.scss | 26 +-- .../functions/_transition-property-name.scss~ | 22 +++ .../util/bourbon/helpers/_render-gradients.scss | 4 +- .../util/bourbon/settings/_asset-pipeline.scss | 1 + .../valo/util/bourbon/settings/_prefixer.scss | 10 +- 27 files changed, 391 insertions(+), 181 deletions(-) delete mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/addons/_rem.scss create mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/addons/_word-wrap.scss create mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/css3/_filter.scss create mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-feature-settings.scss create mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/functions/_color-lightness.scss create mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/functions/_px-to-rem.scss create mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss~ create mode 100644 WebContent/VAADIN/themes/valo/util/bourbon/settings/_asset-pipeline.scss (limited to 'WebContent') diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss b/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss index e97b2fe8d4..c94d48ae14 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss @@ -1,6 +1,12 @@ +// Bourbon 3.2.4 +// http://bourbon.io +// Copyright 2011-2015 thoughtbot, inc. +// MIT License + // Settings @import "settings/prefixer"; @import "settings/px-to-em"; +@import "settings/asset-pipeline"; // Custom Helpers @import "helpers/gradient-positions-parser"; @@ -11,12 +17,14 @@ @import "helpers/shape-size-stripper"; // Custom Functions +@import "functions/color-lightness"; @import "functions/flex-grid"; -@import "functions/grid-width"; @import "functions/golden-ratio"; +@import "functions/grid-width"; @import "functions/linear-gradient"; @import "functions/modular-scale"; @import "functions/px-to-em"; +@import "functions/px-to-rem"; @import "functions/radial-gradient"; @import "functions/strip-units"; @import "functions/tint-shade"; @@ -34,8 +42,10 @@ @import "css3/box-sizing"; @import "css3/calc"; @import "css3/columns"; +@import "css3/filter"; @import "css3/flex-box"; @import "css3/font-face"; +@import "css3/font-feature-settings"; @import "css3/hyphens"; @import "css3/hidpi-media-query"; @import "css3/image-rendering"; @@ -56,14 +66,14 @@ @import "addons/ellipsis"; @import "addons/font-family"; @import "addons/hide-text"; -//@import "addons/html5-input-types"; +@import "addons/html5-input-types"; @import "addons/position"; @import "addons/prefixer"; -@import "addons/rem"; @import "addons/retina-image"; @import "addons/size"; @import "addons/timing-functions"; @import "addons/triangle"; +@import "addons/word-wrap"; // Soon to be deprecated Mixins @import "bourbon-deprecated-upcoming"; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss index fcc39fdf35..14a89e480c 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss @@ -1,38 +1,51 @@ -@mixin button ($style: simple, $base-color: #4294f0) { +@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) { - @if type-of($style) == color { + @if type-of($style) == string and type-of($base-color) == color { + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == string and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: #4294f0; + + @if $padding == inherit { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == color and type-of($base-color) == color { $base-color: $style; $style: simple; + @include buttonstyle($style, $base-color, $text-size, $padding); } - // Grayscale button - @if $base-color == grayscale($base-color) { - @if $style == simple { - @include simple($base-color, $grayscale: true); - } + @if type-of($style) == color and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: $style; + $style: simple; - @else if $style == shiny { - @include shiny($base-color, $grayscale: true); + @if $padding == inherit { + $padding: 7px 18px; } - @else if $style == pill { - @include pill($base-color, $grayscale: true); - } + @include buttonstyle($style, $base-color, $text-size, $padding); } - // Colored button - @else { - @if $style == simple { - @include simple($base-color); - } + @if type-of($style) == number { + $padding: $base-color; + $text-size: $style; + $base-color: #4294f0; + $style: simple; - @else if $style == shiny { - @include shiny($base-color); + @if $padding == #4294f0 { + $padding: 7px 18px; } - @else if $style == pill { - @include pill($base-color); - } + @include buttonstyle($style, $base-color, $text-size, $padding); } &:disabled { @@ -42,16 +55,55 @@ } +// Selector Style Button +//************************************************************************// +@mixin buttonstyle($type, $b-color, $t-size, $pad) { + // Grayscale button + @if $type == simple and $b-color == grayscale($b-color) { + @include simple($b-color, true, $t-size, $pad); + } + + @if $type == shiny and $b-color == grayscale($b-color) { + @include shiny($b-color, true, $t-size, $pad); + } + + @if $type == pill and $b-color == grayscale($b-color) { + @include pill($b-color, true, $t-size, $pad); + } + + @if $type == flat and $b-color == grayscale($b-color) { + @include flat($b-color, true, $t-size, $pad); + } + + // Colored button + @if $type == simple { + @include simple($b-color, false, $t-size, $pad); + } + + @else if $type == shiny { + @include shiny($b-color, false, $t-size, $pad); + } + + @else if $type == pill { + @include pill($b-color, false, $t-size, $pad); + } + + @else if $type == flat { + @include flat($b-color, false, $t-size, $pad); + } +} + + // Simple Button //************************************************************************// -@mixin simple($base-color, $grayscale: false) { +@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { $color: hsl(0, 0, 100%); $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); - @if lightness($base-color) > 70% { + @if is-light($base-color) { $color: hsl(0, 0, 20%); $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); } @@ -68,10 +120,10 @@ box-shadow: inset 0 1px 0 0 $inset-shadow; color: $color; display: inline-block; - font-size: inherit; + font-size: $textsize; font-weight: bold; @include linear-gradient ($base-color, $stop-gradient); - padding: 7px 18px; + padding: $padding; text-decoration: none; text-shadow: 0 1px 0 $text-shadow; background-clip: padding-box; @@ -92,7 +144,8 @@ @include linear-gradient ($base-color-hover, $stop-gradient-hover); } - &:active:not(:disabled) { + &:active:not(:disabled), + &:focus:not(:disabled) { $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); @@ -102,14 +155,14 @@ } border: 1px solid $border-active; - box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active, 0 1px 1px 0 #eee; + box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active; } } // Shiny Button //************************************************************************// -@mixin shiny($base-color, $grayscale: false) { +@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { $color: hsl(0, 0, 100%); $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); @@ -119,7 +172,7 @@ $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); - @if lightness($base-color) > 70% { + @if is-light($base-color) { $color: hsl(0, 0, 20%); $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); } @@ -140,10 +193,10 @@ box-shadow: inset 0 1px 0 0 $inset-shadow; color: $color; display: inline-block; - font-size: inherit; + font-size: $textsize; font-weight: bold; @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); - padding: 8px 20px; + padding: $padding; text-align: center; text-decoration: none; text-shadow: 0 -1px 1px $text-shadow; @@ -168,21 +221,22 @@ $fourth-stop-hover 100%); } - &:active:not(:disabled) { + &:active:not(:disabled), + &:focus:not(:disabled) { $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); @if $grayscale == true { $inset-shadow-active: grayscale($inset-shadow-active); } - box-shadow: inset 0 0 20px 0 $inset-shadow-active, 0 1px 0 #fff; + box-shadow: inset 0 0 20px 0 $inset-shadow-active; } } // Pill Button //************************************************************************// -@mixin pill($base-color, $grayscale: false) { +@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { $color: hsl(0, 0, 100%); $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); @@ -191,7 +245,7 @@ $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); - @if lightness($base-color) > 70% { + @if is-light($base-color) { $color: hsl(0, 0, 20%); $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); } @@ -208,14 +262,14 @@ border: 1px solid $border-top; border-color: $border-top $border-sides $border-bottom; border-radius: 16px; - box-shadow: inset 0 1px 0 0 $inset-shadow, 0 1px 2px 0 #b3b3b3; + box-shadow: inset 0 1px 0 0 $inset-shadow; color: $color; display: inline-block; - font-size: inherit; + font-size: $textsize; font-weight: normal; line-height: 1; @include linear-gradient ($base-color, $stop-gradient); - padding: 5px 16px; + padding: $padding; text-align: center; text-decoration: none; text-shadow: 0 -1px 1px $text-shadow; @@ -249,7 +303,8 @@ background-clip: padding-box; } - &:active:not(:disabled) { + &:active:not(:disabled), + &:focus:not(:disabled) { $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); @@ -267,7 +322,53 @@ background: $active-color; border: 1px solid $border-active; border-bottom: 1px solid $border-bottom-active; - box-shadow: inset 0 0 6px 3px $inset-shadow-active, 0 1px 0 0 #fff; + box-shadow: inset 0 0 6px 3px $inset-shadow-active; text-shadow: 0 -1px 1px $text-shadow-active; } } + + + +// Flat Button +//************************************************************************// +@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + } + + background-color: $base-color; + border-radius: 3px; + border: none; + color: $color; + display: inline-block; + font-size: inherit; + font-weight: bold; + padding: 7px 18px; + text-decoration: none; + background-clip: padding-box; + + &:hover:not(:disabled){ + $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + } + + background-color: $base-color-hover; + cursor: pointer; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + + @if $grayscale == true { + $base-color-active: grayscale($base-color-active); + } + + background-color: $base-color-active; + cursor: pointer; + } +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss index 4818f62fd8..742f1031a4 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss @@ -57,9 +57,6 @@ $right: $pre + "-right" + if($suf, "-#{$suf}", ""); $all: $pre + if($suf, "-#{$suf}", ""); - // Get list inside $vals (is there a better way?) - @each $val in $vals { $vals: $val; } - $vals: collapse-directionals($vals); @if contains-falsy($vals) { @@ -94,21 +91,21 @@ } @mixin margin($vals...) { - @include directional-property(margin, false, $vals); + @include directional-property(margin, false, $vals...); } @mixin padding($vals...) { - @include directional-property(padding, false, $vals); + @include directional-property(padding, false, $vals...); } @mixin border-style($vals...) { - @include directional-property(border, style, $vals); + @include directional-property(border, style, $vals...); } @mixin border-color($vals...) { - @include directional-property(border, color, $vals); + @include directional-property(border, color, $vals...); } @mixin border-width($vals...) { - @include directional-property(border, width, $vals); + @include directional-property(border, width, $vals...); } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss index 26fc879021..8428e4e194 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss @@ -22,7 +22,7 @@ $inputs-list: 'input[type="email"]', $unquoted-inputs-list: (); @each $input-type in $inputs-list { - $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma) !global; + $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma); } $all-text-inputs: $unquoted-inputs-list; @@ -33,7 +33,7 @@ $all-text-inputs: $unquoted-inputs-list; $all-text-inputs-hover: (); @each $input-type in $unquoted-inputs-list { $input-type-hover: $input-type + ":hover"; - $all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma) !global; + $all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma); } // Focus Pseudo-class @@ -41,7 +41,7 @@ $all-text-inputs-hover: (); $all-text-inputs-focus: (); @each $input-type in $unquoted-inputs-list { $input-type-focus: $input-type + ":focus"; - $all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma) !global; + $all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma); } // You must use interpolation on the variable: diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss index aba34edcd9..31a0699769 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss @@ -14,19 +14,19 @@ position: $position; - @if ($top and $top == auto) or (type-of($top) == number and not unitless($top)) { + @if ($top and $top == auto) or (type-of($top) == number and not(unitless($top))) { top: $top; } - @if ($right and $right == auto) or (type-of($right) == number and not unitless($right)) { + @if ($right and $right == auto) or (type-of($right) == number and not(unitless($right))) { right: $right; } - @if ($bottom and $bottom == auto) or (type-of($bottom) == number and not unitless($bottom)) { + @if ($bottom and $bottom == auto) or (type-of($bottom) == number and not(unitless($bottom))) { bottom: $bottom; } - @if ($left and $left == auto) or (type-of($left) == number and not unitless($left)) { + @if ($left and $left == auto) or (type-of($left) == number and not(unitless($left))) { left: $left; } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_rem.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_rem.scss deleted file mode 100644 index ddd7022b44..0000000000 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_rem.scss +++ /dev/null @@ -1,33 +0,0 @@ -@mixin rem($property, $size, $base: $em-base) { - @if not unitless($base) { - $base: strip-units($base); - } - - $unitless_values: (); - @each $num in $size { - @if not unitless($num) { - @if unit($num) == "em" { - $num: $num * $base; - } - - $num: strip-units($num); - } - - $unitless_values: append($unitless_values, $num); - } - $size: $unitless_values; - - $pixel_values: (); - $rem_values: (); - @each $value in $pxval { - $pixel_value: $value * 1px; - $pixel_values: append($pixel_values, $pixel_value); - - $rem_value: ($value / $base) * 1rem; - $rem_values: append($rem_values, $rem_value); - } - - #{$property}: $pixel_values; - #{$property}: $rem_values; -} - diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss index 7931bd1333..3995c1970a 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss @@ -1,4 +1,4 @@ -@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: false) { +@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) { @if $asset-pipeline { background-image: image-url("#{$filename}.#{$extension}"); } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss index 51b2410914..5ecc6f9dcf 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss @@ -1,5 +1,5 @@ // CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) -// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html +// Timing functions are the same as demo'ed here: http://jqueryui.com/resources/demos/effect/easing.html // EASE IN $ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss index 0e02aca2ca..3b29e2c3c0 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss @@ -2,44 +2,85 @@ height: 0; width: 0; + $width: nth($size, 1); + $height: nth($size, length($size)); + + $foreground-color: nth($color, 1); + $background-color: transparent !default; + @if (length($color) == 2) { + $background-color: nth($color, 2); + } + @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { - border-color: transparent; - border-style: solid; - border-width: $size / 2; + + $width: $width / 2; + $height: if(length($size) > 1, $height, $height/2); @if $direction == up { - border-bottom-color: $color; + border-left: $width solid $background-color; + border-right: $width solid $background-color; + border-bottom: $height solid $foreground-color; } @else if $direction == right { - border-left-color: $color; + border-top: $width solid $background-color; + border-bottom: $width solid $background-color; + border-left: $height solid $foreground-color; } @else if $direction == down { - border-top-color: $color; + border-left: $width solid $background-color; + border-right: $width solid $background-color; + border-top: $height solid $foreground-color; } @else if $direction == left { - border-right-color: $color; + border-top: $width solid $background-color; + border-bottom: $width solid $background-color; + border-right: $height solid $foreground-color; } } @else if ($direction == up-right) or ($direction == up-left) { - border-top: $size solid $color; + border-top: $height solid $foreground-color; @if $direction == up-right { - border-left: $size solid transparent; + border-left: $width solid $background-color; } @else if $direction == up-left { - border-right: $size solid transparent; + border-right: $width solid $background-color; } } @else if ($direction == down-right) or ($direction == down-left) { - border-bottom: $size solid $color; + border-bottom: $height solid $foreground-color; @if $direction == down-right { - border-left: $size solid transparent; + border-left: $width solid $background-color; } @else if $direction == down-left { - border-right: $size solid transparent; + border-right: $width solid $background-color; } } + + @else if ($direction == inset-up) { + border-width: $height $width; + border-style: solid; + border-color: $background-color $background-color $foreground-color; + } + + @else if ($direction == inset-down) { + border-width: $height $width; + border-style: solid; + border-color: $foreground-color $background-color $background-color; + } + + @else if ($direction == inset-right) { + border-width: $width $height; + border-style: solid; + border-color: $background-color $background-color $background-color $foreground-color; + } + + @else if ($direction == inset-left) { + border-width: $width $height; + border-style: solid; + border-color: $background-color $foreground-color $background-color $background-color; + } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_word-wrap.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_word-wrap.scss new file mode 100644 index 0000000000..9734a597cd --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_word-wrap.scss @@ -0,0 +1,8 @@ +@mixin word-wrap($wrap: break-word) { + word-wrap: $wrap; + + @if $wrap == break-word { + overflow-wrap: break-word; + word-break: break-all; + } +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss index 42274a4eeb..96f601c1a8 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss @@ -15,7 +15,7 @@ @mixin column-fill($arg: auto) { // auto || length - @include prefixer(columns-fill, $arg, webkit moz spec); + @include prefixer(column-fill, $arg, webkit moz spec); } @mixin column-rule($arg) { diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_filter.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_filter.scss new file mode 100644 index 0000000000..8560d77676 --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_filter.scss @@ -0,0 +1,5 @@ +@mixin filter($function: none) { + // [= 1 { - @include prefixer(transition, $properties, webkit moz spec); + // Fix for vendor-prefix transform property + $needs-prefixes: false; + $webkit: (); + $moz: (); + $spec: (); + + // Create lists for vendor-prefixed transform + @each $list in $properties { + @if nth($list, 1) == "transform" { + $needs-prefixes: true; + $list1: -webkit-transform; + $list2: -moz-transform; + $list3: (); + + @each $var in $list { + $list3: join($list3, $var); + + @if $var != "transform" { + $list1: join($list1, $var); + $list2: join($list2, $var); + } + } + + $webkit: append($webkit, $list1); + $moz: append($moz, $list2); + $spec: append($spec, $list3); + } + + // Create lists for non-prefixed transition properties + @else { + $webkit: append($webkit, $list, comma); + $moz: append($moz, $list, comma); + $spec: append($spec, $list, comma); + } } + @if $needs-prefixes { + -webkit-transition: $webkit; + -moz-transition: $moz; + transition: $spec; + } @else { - $properties: all 0.15s ease-out 0s; - @include prefixer(transition, $properties, webkit moz spec); + @if length($properties) >= 1 { + @include prefixer(transition, $properties, webkit moz spec); + } + + @else { + $properties: all 0.15s ease-out 0s; + @include prefixer(transition, $properties, webkit moz spec); + } } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_color-lightness.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_color-lightness.scss new file mode 100644 index 0000000000..8c6df4e256 --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_color-lightness.scss @@ -0,0 +1,13 @@ +// Programatically determines whether a color is light or dark +// Returns a boolean +// More details here http://robots.thoughtbot.com/closer-look-color-lightness + +@function is-light($hex-color) { + $-local-red: red(rgba($hex-color, 1.0)); + $-local-green: green(rgba($hex-color, 1.0)); + $-local-blue: blue(rgba($hex-color, 1.0)); + + $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255; + + @return $-local-lightness > .6; +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss index 0a7185916c..afc59eb954 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss @@ -1,4 +1,4 @@ -// Scaling Varaibles +// Scaling Variables $golden: 1.618; $minor-second: 1.067; $major-second: 1.125; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_px-to-rem.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_px-to-rem.scss new file mode 100644 index 0000000000..96b244e4cb --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_px-to-rem.scss @@ -0,0 +1,15 @@ +// Convert pixels to rems +// eg. for a relational value of 12px write rem(12) +// Assumes $em-base is the font-size of + +@function rem($pxval) { + @if not unitless($pxval) { + $pxval: strip-units($pxval); + } + + $base: $em-base; + @if not unitless($base) { + $base: strip-units($base); + } + @return ($pxval / $base) * 1rem; +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss index 6ceae72102..49e621d63d 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss @@ -2,21 +2,21 @@ // Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background //************************************************************************// @function transition-property-names($props, $vendor: false) { - $new-props: (); - - @each $prop in $props { - $new-props: append($new-props, transition-property-name($prop, $vendor), comma); - } + $new-props: (); + + @each $prop in $props { + $new-props: append($new-props, transition-property-name($prop, $vendor), comma); + } - @return $new-props; + @return $new-props; } @function transition-property-name($prop, $vendor: false) { - // put other properties that need to be prefixed here aswell - @if $vendor and $prop == transform { - @return unquote('-' + $vendor + '-' + $prop); - } - @else { - @return $prop; - } + // put other properties that need to be prefixed here aswell + @if $vendor and $prop == transform { + @return unquote('-' + $vendor + '-' + $prop); + } + @else { + @return $prop; + } } \ No newline at end of file diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss~ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss~ new file mode 100644 index 0000000000..4b23fccbed --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss~ @@ -0,0 +1,22 @@ +// Return vendor-prefixed property names if appropriate +// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background +//************************************************************************// +@function transition-property-names($props, $vendor: false) { + $new-props: (); + + @each $prop in $props { + $new-props: append($new-props, transition-property-name($prop, $vendor), comma); + } + + @return $new-props; +} + +@function transition-property-name($prop, $vendor: false) { + // put other properties that need to be prefixed here aswell + @if $vendor and $prop == transform { + @return unquote( '-' + $vendor + '-' + $prop); + } + @else { + @return $prop; + } +} \ No newline at end of file diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss b/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss index 5765676838..c145110a17 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss @@ -16,11 +16,11 @@ } @if $vendor { - $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); + $vendor-gradients: "-#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} #{$gradients})"; } @else if $vendor == false { $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; - $vendor-gradients: unquote($vendor-gradients); } + $vendor-gradients: unquote($vendor-gradients); @return $vendor-gradients; } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/settings/_asset-pipeline.scss b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_asset-pipeline.scss new file mode 100644 index 0000000000..d481a6afb1 --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_asset-pipeline.scss @@ -0,0 +1 @@ +$asset-pipeline: false !default; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss index c29a961919..ecab49fb54 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss @@ -1,6 +1,6 @@ // Variable settings for /addons/prefixer.scss -$prefix-for-webkit: true; -$prefix-for-mozilla: true; -$prefix-for-microsoft: true; -$prefix-for-opera: true; -$prefix-for-spec: true; // required for keyframe mixin +$prefix-for-webkit: true !default; +$prefix-for-mozilla: true !default; +$prefix-for-microsoft: true !default; +$prefix-for-opera: true !default; +$prefix-for-spec: true !default; // required for keyframe mixin -- cgit v1.2.3 From 86cf519cd08693444ef3639aeb7b23b30e7aaf25 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 27 Jul 2015 21:48:32 +0300 Subject: Make checkbox inline-block like all other widgets (#18518) Change-Id: Ibec4c7162e9f51baff2534dfc763aa5a83cf915d --- WebContent/VAADIN/themes/base/button/checkbox.scss | 4 -- .../splitpanel/GridLayoutWithCheckbox.java | 58 +++++++++++++++++ .../splitpanel/GridLayoutWithCheckboxTest.java | 75 ++++++++++++++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java create mode 100644 uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckboxTest.java (limited to 'WebContent') diff --git a/WebContent/VAADIN/themes/base/button/checkbox.scss b/WebContent/VAADIN/themes/base/button/checkbox.scss index cc6143dbc1..e46d236035 100644 --- a/WebContent/VAADIN/themes/base/button/checkbox.scss +++ b/WebContent/VAADIN/themes/base/button/checkbox.scss @@ -1,9 +1,5 @@ @mixin base-checkbox($primaryStyleName : v-checkbox) { - .#{$primaryStyleName} { - display: block; - } - .#{$primaryStyleName}, .#{$primaryStyleName} label, .#{$primaryStyleName} input, diff --git a/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java new file mode 100644 index 0000000000..0dc371e57c --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java @@ -0,0 +1,58 @@ +package com.vaadin.tests.components.splitpanel; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.event.FieldEvents.TextChangeEvent; +import com.vaadin.event.FieldEvents.TextChangeListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.AbstractTextField.TextChangeEventMode; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.GridLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; +import com.vaadin.ui.UI; +import com.vaadin.ui.Window; + +@Theme("reindeer") +public class GridLayoutWithCheckbox extends UI { + + @Override + protected void init(VaadinRequest request) { + GridLayout grid = new GridLayout(2, 3); + grid.setWidth(500, Unit.PIXELS); + + Label l = new Label("Textfield 1:"); + grid.addComponent(l, 0, 0); + TextField textfield = new TextField(); + textfield.addTextChangeListener(new TextChangeListener() { + + @Override + public void textChange(TextChangeEvent event) { + + } + }); + textfield.setTextChangeEventMode(TextChangeEventMode.EAGER); + grid.addComponent(textfield, 1, 0); + + l = new Label("CheckBox:"); + grid.addComponent(l, 0, 1); + CheckBox checkBox = new CheckBox(); + grid.addComponent(checkBox, 1, 2); + checkBox.addValueChangeListener(new ValueChangeListener() { + + @Override + public void valueChange(ValueChangeEvent event) { + + } + }); + Window window = new Window(); + window.setWidth(300.0f, Unit.PIXELS); + window.setContent(grid); + window.setResizable(false); + window.setWidth(550, Unit.PIXELS); + + // grid.setColumnExpandRatio(1, 1); + addWindow(window); + } +} \ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckboxTest.java b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckboxTest.java new file mode 100644 index 0000000000..fee46c155a --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckboxTest.java @@ -0,0 +1,75 @@ +/* + * 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.components.splitpanel; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.CheckBoxElement; +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class GridLayoutWithCheckboxTest extends MultiBrowserTest { + + private TextFieldElement tf; + private WebElement tfSlot; + private CheckBoxElement cb; + private WebElement cbSlot; + private Dimension tfSize; + private Dimension tfSlotSize; + private Dimension cbSize; + private Dimension cbSlotSize; + + @Test + public void layoutShouldStayTheSame() { + openTestURL(); + tf = $(TextFieldElement.class).first(); + tfSlot = tf.findElement(By.xpath("..")); + cb = $(CheckBoxElement.class).first(); + cbSlot = cb.findElement(By.xpath("..")); + + // Doing anything with the textfield or checkbox should not affect + // layout + + tf.setValue("a"); + assertSizes(); + cb.click(); + assertSizes(); + tf.setValue("b"); + assertSizes(); + cb.click(); + assertSizes(); + + } + + private void assertSizes() { + if (tfSize == null) { + tfSize = tf.getSize(); + tfSlotSize = tfSlot.getSize(); + cbSize = cb.getSize(); + cbSlotSize = cbSlot.getSize(); + } else { + Assert.assertEquals(tfSize, tf.getSize()); + Assert.assertEquals(tfSlotSize, tfSlot.getSize()); + Assert.assertEquals(cbSize, cb.getSize()); + Assert.assertEquals(cbSlotSize, cbSlot.getSize()); + } + + } +} -- cgit v1.2.3 From 1d36db6d112c818a9c75e7cd10eb6b3519119406 Mon Sep 17 00:00:00 2001 From: patrik Date: Thu, 6 Aug 2015 14:08:38 +0300 Subject: Add better keyboard Close Shortcut API for Window (#17383) Change-Id: I29c7d288fe35f6801cf3576ba06751adce821340 --- WebContent/release-notes.html | 5 + client/src/com/vaadin/client/ui/VWindow.java | 13 +- server/src/com/vaadin/event/ShortcutAction.java | 30 ++- server/src/com/vaadin/ui/Window.java | 238 +++++++++++++++++---- .../vaadin/tests/design/DesignFormatterTest.java | 4 +- .../component/button/ButtonDeclarativeTest.java | 2 +- .../component/window/WindowDeclarativeTest.java | 39 +++- .../components/window/WindowCloseShortcuts.java | 199 +++++++++++++++++ 8 files changed, 467 insertions(+), 63 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/window/WindowCloseShortcuts.java (limited to 'WebContent') diff --git a/WebContent/release-notes.html b/WebContent/release-notes.html index b7cdba7887..61511b3002 100644 --- a/WebContent/release-notes.html +++ b/WebContent/release-notes.html @@ -109,6 +109,11 @@

Incompatible or Behavior-altering Changes in @version-minor@

    +
  • Window close shortcuts have been changed, and ESCAPE is no longer a hard-coded default, but rather a soft one, + and can be removed. If the
    close-shortcut
    attribute of the
    v-window
    element is present, + it must list all close shortcuts, including ESCAPE, separated by whitespace. Existing, unchanged code should + behave as before. See ticket #17383 for more information + on the reasoning behind the change.
  • The push path has been changed from /PUSH/ to /PUSH to be compatible with JSR 356.
  • Widgetset files and other pre-compressed resources are sent as gzip to compatible browsers. This may interfere with custom response compression solutions that do not respect the Content-Encoding response header.
  • diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index e34e12a20b..2d2d6ecee1 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -44,8 +44,6 @@ 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.event.shared.HandlerRegistration; @@ -79,8 +77,7 @@ import com.vaadin.shared.ui.window.WindowRole; * @author Vaadin Ltd */ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, - ScrollHandler, KeyDownHandler, KeyUpHandler, FocusHandler, BlurHandler, - Focusable { + ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable { private static ArrayList windowOrder = new ArrayList(); @@ -221,7 +218,6 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, constructDOM(); contentPanel.addScrollHandler(this); contentPanel.addKeyDownHandler(this); - contentPanel.addKeyUpHandler(this); contentPanel.addFocusHandler(this); contentPanel.addBlurHandler(this); } @@ -1343,13 +1339,6 @@ 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)) { diff --git a/server/src/com/vaadin/event/ShortcutAction.java b/server/src/com/vaadin/event/ShortcutAction.java index 09accae1c7..dd511c23c0 100644 --- a/server/src/com/vaadin/event/ShortcutAction.java +++ b/server/src/com/vaadin/event/ShortcutAction.java @@ -17,6 +17,7 @@ package com.vaadin.event; import java.io.Serializable; +import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -55,7 +56,7 @@ public class ShortcutAction extends Action { private final int keyCode; - private final int[] modifiers; + private int[] modifiers; /** * Creates a shortcut that reacts to the given {@link KeyCode} and @@ -73,7 +74,7 @@ public class ShortcutAction extends Action { public ShortcutAction(String caption, int kc, int... m) { super(caption); keyCode = kc; - modifiers = m; + setModifiers(m); } /** @@ -94,7 +95,7 @@ public class ShortcutAction extends Action { public ShortcutAction(String caption, Resource icon, int kc, int... m) { super(caption, icon); keyCode = kc; - modifiers = m; + setModifiers(m); } /** @@ -190,7 +191,7 @@ public class ShortcutAction extends Action { // Given modifiers override this indicated in the caption if (modifierKeys != null) { - modifiers = modifierKeys; + setModifiers(modifierKeys); } else { // Read modifiers from caption int[] mod = new int[match.length() - 1]; @@ -208,13 +209,30 @@ public class ShortcutAction extends Action { break; } } - modifiers = mod; + setModifiers(mod); } } else { keyCode = -1; - modifiers = modifierKeys; + setModifiers(modifierKeys); } + + } + + /** + * When setting modifiers, make sure that modifiers is a valid array AND + * that it's sorted. + * + * @param modifiers + * the modifier keys for this shortcut + */ + private void setModifiers(int... modifiers) { + if (modifiers == null) { + this.modifiers = new int[0]; + } else { + this.modifiers = modifiers; + } + Arrays.sort(this.modifiers); } /** diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 61ba5826b8..fd5565f900 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -74,7 +75,7 @@ import com.vaadin.util.ReflectTools; * @author Vaadin Ltd. * @since 3.0 */ -@SuppressWarnings("serial") +@SuppressWarnings({ "serial", "deprecation" }) public class Window extends Panel implements FocusNotifier, BlurNotifier, LegacyComponent { @@ -101,6 +102,11 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, } }; + /** + * Holds registered CloseShortcut instances for query and later removal + */ + private List closeShortcuts = new ArrayList(4); + /** * Creates a new, empty window */ @@ -130,6 +136,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, super(caption, content); registerRpc(rpc); setSizeUndefined(); + setCloseShortcut(KeyCode.ESCAPE); } /* ********************************************************************* */ @@ -828,14 +835,22 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, } } - /* - * Actions - */ - protected CloseShortcut closeShortcut; - /** - * Makes is possible to close the window by pressing the given - * {@link KeyCode} and (optional) {@link ModifierKey}s.
    + * This is the old way of adding a keyboard shortcut to close a + * {@link Window} - to preserve compatibility with existing code under the + * new functionality, this method now first removes all registered close + * shortcuts, then adds the default ESCAPE shortcut key, and then attempts + * to add the shortcut provided as parameters to this method. This method, + * and its companion {@link #removeCloseShortcut()}, are now considered + * deprecated, as their main function is to preserve exact backwards + * compatibility with old code. For all new code, use the new keyboard + * shortcuts API: {@link #addCloseShortcut(int,int...)}, + * {@link #removeCloseShortcut(int,int...)}, + * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)} + * and {@link #getCloseShortcuts()}. + *

    + * Original description: Makes it possible to close the window by pressing + * the given {@link KeyCode} and (optional) {@link ModifierKey}s.
    * Note that this shortcut only reacts while the window has focus, closing * itself - if you want to close a window from a UI, use * {@link UI#addAction(com.vaadin.event.Action)} of the UI instead. @@ -843,28 +858,136 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * @param keyCode * the keycode for invoking the shortcut * @param modifiers - * the (optional) modifiers for invoking the shortcut, null for - * none + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + * + * @deprecated Use {@link #addCloseShortcut(int, int...)} instead. */ + @Deprecated public void setCloseShortcut(int keyCode, int... modifiers) { - if (closeShortcut != null) { - removeAction(closeShortcut); - } - closeShortcut = new CloseShortcut(this, keyCode, modifiers); - addAction(closeShortcut); + removeCloseShortcut(); + addCloseShortcut(keyCode, modifiers); } /** - * Removes the keyboard shortcut previously set with - * {@link #setCloseShortcut(int, int...)}. + * Removes all keyboard shortcuts previously set with + * {@link #setCloseShortcut(int, int...)} and + * {@link #addCloseShortcut(int, int...)}, then adds the default + * {@link KeyCode#ESCAPE} shortcut. + *

    + * This is the old way of removing the (single) keyboard close shortcut, and + * is retained only for exact backwards compatibility. For all new code, use + * the new keyboard shortcuts API: {@link #addCloseShortcut(int,int...)}, + * {@link #removeCloseShortcut(int,int...)}, + * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)} + * and {@link #getCloseShortcuts()}. + * + * @deprecated Use {@link #removeCloseShortcut(int, int...)} instead. */ + @Deprecated public void removeCloseShortcut() { - if (closeShortcut != null) { - removeAction(closeShortcut); - closeShortcut = null; + for (int i = 0; i < closeShortcuts.size(); ++i) { + CloseShortcut sc = closeShortcuts.get(i); + removeAction(sc); + } + closeShortcuts.clear(); + addCloseShortcut(KeyCode.ESCAPE); + } + + /** + * Adds a close shortcut - pressing this key while holding down all (if any) + * modifiers specified while this Window is in focus will close the Window. + * + * @since + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + */ + public void addCloseShortcut(int keyCode, int... modifiers) { + + // Ignore attempts to re-add existing shortcuts + if (hasCloseShortcut(keyCode, modifiers)) { + return; + } + + // Actually add the shortcut + CloseShortcut shortcut = new CloseShortcut(this, keyCode, modifiers); + addAction(shortcut); + closeShortcuts.add(shortcut); + } + + /** + * Removes a close shortcut previously added with + * {@link #addCloseShortcut(int, int...)}. + * + * @since + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + */ + public void removeCloseShortcut(int keyCode, int... modifiers) { + for (CloseShortcut shortcut : closeShortcuts) { + if (shortcut.equals(keyCode, modifiers)) { + removeAction(shortcut); + closeShortcuts.remove(shortcut); + return; + } } } + /** + * Removes all close shortcuts. This includes the default ESCAPE shortcut. + * It is up to the user to add back any and all keyboard close shortcuts + * they may require. For more fine-grained control over shortcuts, use + * {@link #removeCloseShortcut(int, int...)}. + * + * @since + */ + public void removeAllCloseShortcuts() { + for (CloseShortcut shortcut : closeShortcuts) { + removeAction(shortcut); + } + closeShortcuts.clear(); + } + + /** + * Checks if a close window shortcut key has already been registered. + * + * @since + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + * @return true, if an exactly matching shortcut has been registered. + */ + public boolean hasCloseShortcut(int keyCode, int... modifiers) { + for (CloseShortcut shortcut : closeShortcuts) { + if (shortcut.equals(keyCode, modifiers)) { + return true; + } + } + return false; + } + + /** + * Returns an unmodifiable collection of {@link CloseShortcut} objects + * currently registered with this {@link Window}. This method is provided + * mainly so that users can implement their own serialization routines. To + * check if a certain combination of keys has been registered as a close + * shortcut, use the {@link #hasCloseShortcut(int, int...)} method instead. + * + * @since + * @return an unmodifiable Collection of CloseShortcut objects. + */ + public Collection getCloseShortcuts() { + return Collections.unmodifiableCollection(closeShortcuts); + } + /** * A {@link ShortcutListener} specifically made to define a keyboard * shortcut that closes the window. @@ -930,6 +1053,25 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, public void handleAction(Object sender, Object target) { window.close(); } + + public boolean equals(int keyCode, int... modifiers) { + if (keyCode != getKeyCode()) { + return false; + } + + if (getModifiers() != null) { + int[] mods = null; + if (modifiers != null) { + // Modifiers provided by the parent ShortcutAction class + // are guaranteed to be sorted. We still need to sort + // the modifiers passed in as argument. + mods = Arrays.copyOf(modifiers, modifiers.length); + Arrays.sort(mods); + } + return Arrays.equals(mods, getModifiers()); + } + return true; + } } /* @@ -1244,11 +1386,26 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, setPositionX(Integer.parseInt(position[0])); setPositionY(Integer.parseInt(position[1])); } + + // Parse shortcuts if defined, otherwise rely on default behavior if (design.hasAttr("close-shortcut")) { - ShortcutAction shortcut = DesignAttributeHandler - .readAttribute("close-shortcut", design.attributes(), - ShortcutAction.class); - setCloseShortcut(shortcut.getKeyCode(), shortcut.getModifiers()); + + // Parse shortcuts + String[] shortcutStrings = DesignAttributeHandler.readAttribute( + "close-shortcut", design.attributes(), String.class).split( + "\\s+"); + + removeAllCloseShortcuts(); + + for (String part : shortcutStrings) { + if (!part.isEmpty()) { + ShortcutAction shortcut = DesignAttributeHandler + .getFormatter().parse(part.trim(), + ShortcutAction.class); + addCloseShortcut(shortcut.getKeyCode(), + shortcut.getModifiers()); + } + } } } @@ -1302,19 +1459,24 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, DesignAttributeHandler.writeAttribute("position", design.attributes(), getPosition(), def.getPosition(), String.class); - CloseShortcut shortcut = getCloseShortcut(); - if (shortcut != null) { - // TODO What if several close shortcuts?? - - CloseShortcut defShortcut = def.getCloseShortcut(); - if (defShortcut == null - || shortcut.getKeyCode() != defShortcut.getKeyCode() - || !Arrays.equals(shortcut.getModifiers(), - defShortcut.getModifiers())) { - DesignAttributeHandler.writeAttribute("close-shortcut", - design.attributes(), shortcut, null, - CloseShortcut.class); + // Process keyboard shortcuts + if (closeShortcuts.size() == 1 && hasCloseShortcut(KeyCode.ESCAPE)) { + // By default, we won't write anything if we're relying on default + // shortcut behavior + } else { + // Dump all close shortcuts to a string... + String attrString = ""; + + // TODO: add canonical support for array data in XML attributes + for (CloseShortcut shortcut : closeShortcuts) { + String shortcutString = DesignAttributeHandler.getFormatter() + .format(shortcut, CloseShortcut.class); + attrString += shortcutString + " "; } + + // Write everything except the last "," to the design + DesignAttributeHandler.writeAttribute("close-shortcut", + design.attributes(), attrString.trim(), null, String.class); } for (Component c : getAssistiveDescription()) { @@ -1328,10 +1490,6 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, return getPositionX() + "," + getPositionY(); } - private CloseShortcut getCloseShortcut() { - return closeShortcut; - } - @Override protected Collection getCustomAttributes() { Collection result = super.getCustomAttributes(); diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java index acee3e2ca8..6510d8ad40 100644 --- a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java +++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java @@ -203,7 +203,7 @@ public class DesignFormatterTest { ShortcutAction action = new ShortcutAction("&^d"); String formatted = formatter.format(action); // note the space here - it separates key combination from caption - assertEquals("alt-ctrl-d d", formatted); + assertEquals("ctrl-alt-d d", formatted); ShortcutAction result = formatter .parse(formatted, ShortcutAction.class); @@ -215,7 +215,7 @@ public class DesignFormatterTest { ShortcutAction action = new ShortcutAction(null, KeyCode.D, new int[] { ModifierKey.ALT, ModifierKey.CTRL }); String formatted = formatter.format(action); - assertEquals("alt-ctrl-d", formatted); + assertEquals("ctrl-alt-d", formatted); ShortcutAction result = formatter .parse(formatted, ShortcutAction.class); diff --git a/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java index 51abf6be96..b1c10789b4 100644 --- a/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java +++ b/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java @@ -98,7 +98,7 @@ public class ButtonDeclarativeTest extends DeclarativeTestBase

Known Issues and Limitations

    diff --git a/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java new file mode 100644 index 0000000000..8ca2292bc5 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java @@ -0,0 +1,82 @@ +/* + * 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.client.connectors; + +import java.util.Collection; + +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.extensions.AbstractExtensionConnector; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.ui.grid.GridState; + +import elemental.json.JsonObject; + +/** + * Base class for all selection model connectors. + * + * @since + * @author Vaadin Ltd + */ +public abstract class AbstractSelectionModelConnector> + extends AbstractExtensionConnector { + + @Override + public GridConnector getParent() { + return (GridConnector) super.getParent(); + } + + protected Grid getGrid() { + return getParent().getWidget(); + } + + protected RowHandle getRowHandle(JsonObject row) { + return getGrid().getDataSource().getHandle(row); + } + + protected String getRowKey(JsonObject row) { + return row != null ? getParent().getRowKey(row) : null; + } + + protected abstract T createSelectionModel(); + + public abstract static class AbstractSelectionModel implements + SelectionModel { + + @Override + public boolean isSelected(JsonObject row) { + return row.hasKey(GridState.JSONKEY_SELECTED); + } + + @Override + public void setGrid(Grid grid) { + // NO-OP + } + + @Override + public void reset() { + // Should not need any actions. + } + + @Override + public Collection getSelectedRows() { + throw new UnsupportedOperationException( + "This client-side selection model " + + getClass().getSimpleName() + + " does not know selected rows."); + } + } +} diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index 5f9341c068..1070a46287 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -22,7 +22,6 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -36,7 +35,6 @@ import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Timer; -import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; @@ -48,8 +46,6 @@ import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource; -import com.vaadin.client.data.DataSource.RowHandle; -import com.vaadin.client.renderers.Renderer; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.AbstractHasComponentsConnector; import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; @@ -72,15 +68,6 @@ import com.vaadin.client.widget.grid.events.EditorMoveEvent; import com.vaadin.client.widget.grid.events.EditorOpenEvent; import com.vaadin.client.widget.grid.events.GridClickEvent; import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; -import com.vaadin.client.widget.grid.events.SelectAllEvent; -import com.vaadin.client.widget.grid.events.SelectAllHandler; -import com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel; -import com.vaadin.client.widget.grid.selection.SelectionEvent; -import com.vaadin.client.widget.grid.selection.SelectionHandler; -import com.vaadin.client.widget.grid.selection.SelectionModel; -import com.vaadin.client.widget.grid.selection.SelectionModelMulti; -import com.vaadin.client.widget.grid.selection.SelectionModelNone; -import com.vaadin.client.widget.grid.selection.SelectionModelSingle; import com.vaadin.client.widget.grid.sort.SortEvent; import com.vaadin.client.widget.grid.sort.SortHandler; import com.vaadin.client.widget.grid.sort.SortOrder; @@ -99,7 +86,6 @@ import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridConstants; import com.vaadin.shared.ui.grid.GridServerRpc; import com.vaadin.shared.ui.grid.GridState; -import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode; import com.vaadin.shared.ui.grid.GridStaticSectionState; import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState; import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState; @@ -584,19 +570,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements */ private Map columnIdToColumn = new HashMap(); - private AbstractRowHandleSelectionModel selectionModel; - private Set selectedKeys = new LinkedHashSet(); private List columnOrder = new ArrayList(); - /** - * {@link #selectionUpdatedFromState} is set to true when - * {@link #updateSelectionFromState()} makes changes to selection. This flag - * tells the {@code internalSelectionChangeHandler} to not send same data - * straight back to server. Said listener sets it back to false when - * handling that event. - */ - private boolean selectionUpdatedFromState; - /** * {@link #columnsUpdatedFromState} is set to true when * {@link #updateColumnOrderFromState(List)} is updating the column order @@ -608,29 +583,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements private RpcDataSource dataSource; - private SelectionHandler internalSelectionChangeHandler = new SelectionHandler() { - @Override - public void onSelect(SelectionEvent event) { - if (event.isBatchedSelection()) { - return; - } - if (!selectionUpdatedFromState) { - for (JsonObject row : event.getRemoved()) { - selectedKeys.remove(dataSource.getRowKey(row)); - } - - for (JsonObject row : event.getAdded()) { - selectedKeys.add(dataSource.getRowKey(row)); - } - - getRpcProxy(GridServerRpc.class).select( - new ArrayList(selectedKeys)); - } else { - selectionUpdatedFromState = false; - } - } - }; - /* Used to track Grid editor columns with validation errors */ private final Map, String> columnToErrorMessage = new HashMap, String>(); @@ -744,30 +696,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void recalculateColumnWidths() { getWidget().recalculateColumnWidths(); } - - @Override - public void setSelectAll(boolean allSelected) { - if (selectionModel instanceof SelectionModel.Multi - && selectionModel.getSelectionColumnRenderer() != null) { - final Widget widget; - try { - HeaderRow defaultHeaderRow = getWidget() - .getDefaultHeaderRow(); - if (defaultHeaderRow != null) { - widget = defaultHeaderRow.getCell( - getWidget().getColumn(0)).getWidget(); - ((CheckBox) widget).setValue(allSelected, false); - } - } catch (Exception e) { - getLogger().warning( - "Problems finding select all checkbox."); - } - } - } }); - getWidget().addSelectionHandler(internalSelectionChangeHandler); - /* Item click events */ getWidget().addBodyClickHandler(itemClickHandler); getWidget().addBodyDoubleClickHandler(itemClickHandler); @@ -800,15 +730,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } }); - getWidget().addSelectAllHandler(new SelectAllHandler() { - - @Override - public void onSelectAll(SelectAllEvent event) { - getRpcProxy(GridServerRpc.class).selectAll(); - } - - }); - getWidget().setEditorHandler(editorHandler); getWidget().addColumnReorderHandler(columnReorderHandler); getWidget().addColumnVisibilityChangeHandler( @@ -884,19 +805,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements updateFooterFromState(getState().footer); } - // Selection - if (stateChangeEvent.hasPropertyChanged("selectionMode")) { - onSelectionModeChange(); - updateSelectDeselectAllowed(); - } else if (stateChangeEvent - .hasPropertyChanged("singleSelectDeselectAllowed")) { - updateSelectDeselectAllowed(); - } - - if (stateChangeEvent.hasPropertyChanged("selectedKeys")) { - updateSelectionFromState(); - } - // Sorting if (stateChangeEvent.hasPropertyChanged("sortColumns") || stateChangeEvent.hasPropertyChanged("sortDirs")) { @@ -923,14 +831,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - private void updateSelectDeselectAllowed() { - SelectionModel model = getWidget().getSelectionModel(); - if (model instanceof SelectionModel.Single) { - ((SelectionModel.Single) model) - .setDeselectAllowed(getState().singleSelectDeselectAllowed); - } - } - private void updateColumnOrderFromState(List stateColumnOrder) { CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder .size()]; @@ -1102,20 +1002,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements columnOrder.add(state.id); } - /** - * If we have a selection column renderer, we need to offset the index by - * one when referring to the column index in the widget. - */ - private int getWidgetColumnIndex(final int columnIndex) { - Renderer selectionColumnRenderer = getWidget() - .getSelectionModel().getSelectionColumnRenderer(); - int widgetColumnIndex = columnIndex; - if (selectionColumnRenderer != null) { - widgetColumnIndex++; - } - return widgetColumnIndex; - } - /** * Updates the column values from a state * @@ -1178,63 +1064,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().setDataSource(this.dataSource); } - private void onSelectionModeChange() { - SharedSelectionMode mode = getState().selectionMode; - if (mode == null) { - getLogger().fine("ignored mode change"); - return; - } - - AbstractRowHandleSelectionModel model = createSelectionModel(mode); - if (selectionModel == null - || !model.getClass().equals(selectionModel.getClass())) { - selectionModel = model; - getWidget().setSelectionModel(model); - selectedKeys.clear(); - } - } - - private void updateSelectionFromState() { - boolean changed = false; - - List stateKeys = getState().selectedKeys; - - // find new deselections - for (String key : selectedKeys) { - if (!stateKeys.contains(key)) { - changed = true; - deselectByHandle(dataSource.getHandleByKey(key)); - } - } - - // find new selections - for (String key : stateKeys) { - if (!selectedKeys.contains(key)) { - changed = true; - selectByHandle(dataSource.getHandleByKey(key)); - } - } - - /* - * A defensive copy in case the collection in the state is mutated - * instead of re-assigned. - */ - selectedKeys = new LinkedHashSet(stateKeys); - - /* - * We need to fire this event so that Grid is able to re-render the - * selection changes (if applicable). - */ - if (changed) { - // At least for now there's no way to send the selected and/or - // deselected row data. Some data is only stored as keys - selectionUpdatedFromState = true; - getWidget().fireEvent( - new SelectionEvent(getWidget(), - (List) null, null, false)); - } - } - private void onSortStateChange() { List sortOrder = new ArrayList(); @@ -1253,41 +1082,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements return Logger.getLogger(getClass().getName()); } - @SuppressWarnings("static-method") - private AbstractRowHandleSelectionModel createSelectionModel( - SharedSelectionMode mode) { - switch (mode) { - case SINGLE: - return new SelectionModelSingle(); - case MULTI: - return new SelectionModelMulti(); - case NONE: - return new SelectionModelNone(); - default: - throw new IllegalStateException("unexpected mode value: " + mode); - } - } - - /** - * A workaround method for accessing the protected method - * {@code AbstractRowHandleSelectionModel.selectByHandle} - */ - private native void selectByHandle(RowHandle handle) - /*-{ - var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel; - model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::selectByHandle(*)(handle); - }-*/; - - /** - * A workaround method for accessing the protected method - * {@code AbstractRowHandleSelectionModel.deselectByHandle} - */ - private native void deselectByHandle(RowHandle handle) - /*-{ - var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel; - model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle); - }-*/; - /** * Gets the row key for a row object. * @@ -1312,7 +1106,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void updateCaption(ComponentConnector connector) { // TODO Auto-generated method stub - } @Override diff --git a/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java new file mode 100644 index 0000000000..c1f5d87d68 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java @@ -0,0 +1,360 @@ +/* + * 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.client.connectors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.CheckBox; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.data.DataSource; +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.ComplexRenderer; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.DataAvailableEvent; +import com.vaadin.client.widget.grid.DataAvailableHandler; +import com.vaadin.client.widget.grid.events.SelectAllEvent; +import com.vaadin.client.widget.grid.events.SelectAllHandler; +import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; +import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.HeaderCell; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.GridState; +import com.vaadin.shared.ui.grid.Range; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; +import com.vaadin.ui.Grid.MultiSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link MultiSelectionModel}. + * + * @since + * @author Vaadin Ltd + */ +@Connect(MultiSelectionModel.class) +public class MultiSelectionModelConnector extends + AbstractSelectionModelConnector> { + + private Multi selectionModel = createSelectionModel(); + private SpaceSelectHandler spaceHandler; + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(selectionModel); + spaceHandler = new SpaceSelectHandler(getGrid()); + } + + @Override + public void onUnregister() { + spaceHandler.removeHandler(); + } + + @Override + protected Multi createSelectionModel() { + return new MultiSelectionModel(); + } + + @Override + public MultiSelectionModelState getState() { + return (MultiSelectionModelState) super.getState(); + } + + @OnStateChange("allSelected") + void updateSelectAllCheckbox() { + if (selectionModel.getSelectionColumnRenderer() != null) { + HeaderCell cell = getGrid().getDefaultHeaderRow().getCell( + getGrid().getColumn(0)); + CheckBox widget = (CheckBox) cell.getWidget(); + widget.setValue(getState().allSelected, false); + } + } + + protected class MultiSelectionModel extends AbstractSelectionModel + implements SelectionModel.Multi.Batched { + + private ComplexRenderer renderer = null; + private Set> selected = new HashSet>(); + private Set> deselected = new HashSet>(); + private HandlerRegistration selectAll; + private HandlerRegistration dataAvailable; + private Range availableRows; + private boolean batchSelect = false; + + @Override + public void setGrid(Grid grid) { + super.setGrid(grid); + if (grid != null) { + renderer = createSelectionColumnRenderer(grid); + selectAll = getGrid().addSelectAllHandler( + new SelectAllHandler() { + + @Override + public void onSelectAll( + SelectAllEvent event) { + selectAll(); + } + }); + dataAvailable = getGrid().addDataAvailableHandler( + new DataAvailableHandler() { + + @Override + public void onDataAvailable(DataAvailableEvent event) { + availableRows = event.getAvailableRows(); + } + }); + } else if (renderer != null) { + renderer.destroy(); + selectAll.removeHandler(); + dataAvailable.removeHandler(); + renderer = null; + } + } + + /** + * Creates a selection column renderer. This method can be overridden to + * use a custom renderer or use {@code null} to disable the selection + * column. + * + * @param grid + * the grid for this selection model + * @return selection column renderer or {@code null} if not needed + */ + protected ComplexRenderer createSelectionColumnRenderer( + Grid grid) { + return new MultiSelectionRenderer(grid); + } + + /** + * Selects all available rows, sends request to server to select + * everything. + */ + public void selectAll() { + assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection."; + + Set> rows = new HashSet>(); + DataSource dataSource = getGrid().getDataSource(); + for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { + final JsonObject row = dataSource.getRow(i); + if (row != null) { + RowHandle handle = dataSource.getHandle(row); + markAsSelected(handle, true); + rows.add(handle); + } + } + + getRpcProxy(MultiSelectionModelServerRpc.class).selectAll(); + cleanRowCache(rows); + } + + @Override + public Renderer getSelectionColumnRenderer() { + return renderer; + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean select(JsonObject... rows) { + return select(Arrays.asList(rows)); + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean deselect(JsonObject... rows) { + return deselect(Arrays.asList(rows)); + } + + /** + * {@inheritDoc} + * + * @return always {@code true} + */ + @Override + public boolean deselectAll() { + assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection."; + + Set> rows = new HashSet>(); + DataSource dataSource = getGrid().getDataSource(); + for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { + final JsonObject row = dataSource.getRow(i); + if (row != null) { + RowHandle handle = dataSource.getHandle(row); + markAsSelected(handle, false); + rows.add(handle); + } + } + + getRpcProxy(MultiSelectionModelServerRpc.class).deselectAll(); + cleanRowCache(rows); + + return true; + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean select(Collection rows) { + if (rows.isEmpty()) { + return false; + } + + for (JsonObject row : rows) { + RowHandle rowHandle = getRowHandle(row); + markAsSelected(rowHandle, true); + selected.add(rowHandle); + } + + if (!isBeingBatchSelected()) { + sendSelected(); + } + return true; + } + + /** + * Marks the JsonObject pointed by RowHandle to have selected + * information equal to given boolean + * + * @param row + * row handle + * @param selected + * should row be selected + */ + protected void markAsSelected(RowHandle row, + boolean selected) { + row.pin(); + if (selected) { + row.getRow().put(GridState.JSONKEY_SELECTED, true); + } else { + row.getRow().remove(GridState.JSONKEY_SELECTED); + } + row.updateRow(); + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean deselect(Collection rows) { + if (rows.isEmpty()) { + return false; + } + + for (JsonObject row : rows) { + RowHandle rowHandle = getRowHandle(row); + markAsSelected(rowHandle, false); + deselected.add(rowHandle); + } + + if (!isBeingBatchSelected()) { + sendDeselected(); + } + return true; + } + + private void sendDeselected() { + getRpcProxy(MultiSelectionModelServerRpc.class).deselect( + getRowKeys(deselected)); + cleanRowCache(deselected); + } + + private void sendSelected() { + getRpcProxy(MultiSelectionModelServerRpc.class).select( + getRowKeys(selected)); + cleanRowCache(selected); + } + + private List getRowKeys(Set> handles) { + List keys = new ArrayList(); + for (RowHandle handle : handles) { + keys.add(getRowKey(handle.getRow())); + } + return keys; + } + + private Set getRows(Set> handles) { + Set rows = new HashSet(); + for (RowHandle handle : handles) { + rows.add(handle.getRow()); + } + return rows; + } + + private void cleanRowCache(Set> handles) { + for (RowHandle handle : handles) { + handle.unpin(); + } + handles.clear(); + } + + @Override + public void startBatchSelect() { + assert selected.isEmpty() && deselected.isEmpty() : "Row caches were not clear."; + batchSelect = true; + } + + @Override + public void commitBatchSelect() { + assert batchSelect : "Not batch selecting."; + if (!selected.isEmpty()) { + sendSelected(); + } + + if (!deselected.isEmpty()) { + sendDeselected(); + } + batchSelect = false; + } + + @Override + public boolean isBeingBatchSelected() { + return batchSelect; + } + + @Override + public Collection getSelectedRowsBatch() { + return Collections.unmodifiableSet(getRows(selected)); + } + + @Override + public Collection getDeselectedRowsBatch() { + return Collections.unmodifiableSet(getRows(deselected)); + } + } +} diff --git a/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java new file mode 100644 index 0000000000..b540eed6d5 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java @@ -0,0 +1,42 @@ +/* + * 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.client.connectors; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModelNone; +import com.vaadin.shared.ui.Connect; +import com.vaadin.ui.Grid.NoSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link NoSelectionModel}. + */ +@Connect(NoSelectionModel.class) +public class NoSelectionModelConnector extends + AbstractSelectionModelConnector> { + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(createSelectionModel()); + } + + @Override + protected SelectionModel createSelectionModel() { + return new SelectionModelNone(); + } +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index 78aaebaca1..bcca8816b1 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -99,8 +99,10 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { } @Override - public void updateRowData(JsonObject row) { - RpcDataSource.this.updateRowData(row); + public void updateRowData(JsonArray rowArray) { + for (int i = 0; i < rowArray.length(); ++i) { + RpcDataSource.this.updateRowData(rowArray.getObject(i)); + } } }); } diff --git a/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java new file mode 100644 index 0000000000..7c66903c2c --- /dev/null +++ b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java @@ -0,0 +1,148 @@ +/* + * 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.client.connectors; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.selection.ClickSelectHandler; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModel.Single; +import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.GridState; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState; +import com.vaadin.ui.Grid.SingleSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link SingleSelectionModel}. + * + * @since + * @author Vaadin Ltd + */ +@Connect(SingleSelectionModel.class) +public class SingleSelectionModelConnector extends + AbstractSelectionModelConnector> { + + private SpaceSelectHandler spaceHandler; + private ClickSelectHandler clickHandler; + private Single selectionModel = createSelectionModel(); + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(selectionModel); + spaceHandler = new SpaceSelectHandler(getGrid()); + clickHandler = new ClickSelectHandler(getGrid()); + } + + @Override + public SingleSelectionModelState getState() { + return (SingleSelectionModelState) super.getState(); + } + + @Override + public void onUnregister() { + spaceHandler.removeHandler(); + clickHandler.removeHandler(); + + super.onUnregister(); + } + + @Override + protected Single createSelectionModel() { + return new SingleSelectionModel(); + } + + @OnStateChange("deselectAllowed") + void updateDeselectAllowed() { + selectionModel.setDeselectAllowed(getState().deselectAllowed); + } + + /** + * SingleSelectionModel without a selection column renderer. + */ + public class SingleSelectionModel extends AbstractSelectionModel implements + SelectionModel.Single { + + private RowHandle selectedRow; + private boolean deselectAllowed; + + @Override + public Renderer getSelectionColumnRenderer() { + return null; + } + + @Override + public boolean select(JsonObject row) { + boolean changed = false; + if ((row == null && isDeselectAllowed()) + || (row != null && !getRowHandle(row).equals(selectedRow))) { + if (selectedRow != null) { + selectedRow.getRow().remove(GridState.JSONKEY_SELECTED); + selectedRow.updateRow(); + selectedRow.unpin(); + selectedRow = null; + changed = true; + } + + if (row != null) { + selectedRow = getRowHandle(row); + selectedRow.pin(); + selectedRow.getRow().put(GridState.JSONKEY_SELECTED, true); + selectedRow.updateRow(); + changed = true; + } + } + + if (changed) { + getRpcProxy(SingleSelectionModelServerRpc.class).select( + getRowKey(row)); + } + + return changed; + } + + @Override + public boolean deselect(JsonObject row) { + if (getRowHandle(row).equals(selectedRow)) { + select(null); + } + return false; + } + + @Override + public JsonObject getSelectedRow() { + throw new UnsupportedOperationException( + "This client-side selection model " + + getClass().getSimpleName() + + " does not know selected row."); + } + + @Override + public void setDeselectAllowed(boolean deselectAllowed) { + this.deselectAllowed = deselectAllowed; + } + + @Override + public boolean isDeselectAllowed() { + return deselectAllowed; + } + } +} \ No newline at end of file diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index a2eedf4203..91fc87d2c7 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -5287,7 +5287,7 @@ public class Grid extends ResizeComposite implements @Override public void preDetach(Row row, Iterable cellsToDetach) { for (FlyweightCell cell : cellsToDetach) { - Renderer renderer = findRenderer(cell); + Renderer renderer = findRenderer(cell); if (renderer instanceof WidgetRenderer) { try { Widget w = WidgetUtil.findWidget(cell.getElement() @@ -5317,7 +5317,7 @@ public class Grid extends ResizeComposite implements // any more rowReference.set(rowIndex, null, row.getElement()); for (FlyweightCell cell : detachedCells) { - Renderer renderer = findRenderer(cell); + Renderer renderer = findRenderer(cell); if (renderer instanceof ComplexRenderer) { try { Column column = getVisibleColumn(cell.getColumn()); @@ -7233,12 +7233,12 @@ public class Grid extends ResizeComposite implements * if the current selection model is not an instance of * {@link SelectionModel.Single} or {@link SelectionModel.Multi} */ - @SuppressWarnings("unchecked") public boolean select(T row) { if (selectionModel instanceof SelectionModel.Single) { return ((SelectionModel.Single) selectionModel).select(row); } else if (selectionModel instanceof SelectionModel.Multi) { - return ((SelectionModel.Multi) selectionModel).select(row); + return ((SelectionModel.Multi) selectionModel) + .select(Collections.singleton(row)); } else { throw new IllegalStateException("Unsupported selection model"); } @@ -7258,12 +7258,12 @@ public class Grid extends ResizeComposite implements * if the current selection model is not an instance of * {@link SelectionModel.Single} or {@link SelectionModel.Multi} */ - @SuppressWarnings("unchecked") public boolean deselect(T row) { if (selectionModel instanceof SelectionModel.Single) { return ((SelectionModel.Single) selectionModel).deselect(row); } else if (selectionModel instanceof SelectionModel.Multi) { - return ((SelectionModel.Multi) selectionModel).deselect(row); + return ((SelectionModel.Multi) selectionModel) + .deselect(Collections.singleton(row)); } else { throw new IllegalStateException("Unsupported selection model"); } diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index f5d712f6b2..c8f3604fd9 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -744,15 +744,11 @@ public class RpcDataProviderExtension extends AbstractExtension { // Send current rows again if needed. if (refreshCache) { - for (Object itemId : activeItemHandler.getActiveItemIds()) { - internalUpdateRowData(itemId); - } + updatedItemIds.addAll(activeItemHandler.getActiveItemIds()); } } - for (Object itemId : updatedItemIds) { - internalUpdateRowData(itemId); - } + internalUpdateRows(updatedItemIds); // Clear all changes. rowChanges.clear(); @@ -913,11 +909,20 @@ public class RpcDataProviderExtension extends AbstractExtension { updatedItemIds.add(itemId); } - private void internalUpdateRowData(Object itemId) { - if (activeItemHandler.getActiveItemIds().contains(itemId)) { - JsonObject row = getRowData(getGrid().getColumns(), itemId); - rpc.updateRowData(row); + private void internalUpdateRows(Set itemIds) { + if (itemIds.isEmpty()) { + return; + } + + JsonArray rowData = Json.createArray(); + int i = 0; + for (Object itemId : itemIds) { + if (activeItemHandler.getActiveItemIds().contains(itemId)) { + JsonObject row = getRowData(getGrid().getColumns(), itemId); + rowData.set(i++, row); + } } + rpc.updateRowData(rowData); } /** diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index d9c011677b..f58280c6fe 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -82,6 +82,7 @@ import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.AbstractExtension; import com.vaadin.server.EncodeResult; import com.vaadin.server.ErrorMessage; +import com.vaadin.server.Extension; import com.vaadin.server.JsonCodec; import com.vaadin.server.KeyMapper; import com.vaadin.server.VaadinSession; @@ -94,13 +95,16 @@ import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridConstants; import com.vaadin.shared.ui.grid.GridServerRpc; import com.vaadin.shared.ui.grid.GridState; -import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode; import com.vaadin.shared.ui.grid.GridStaticCellType; import com.vaadin.shared.ui.grid.GridStaticSectionState; import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState; import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState; import com.vaadin.shared.ui.grid.HeightMode; import com.vaadin.shared.ui.grid.ScrollDestination; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState; import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; @@ -111,7 +115,6 @@ import com.vaadin.ui.renderers.TextRenderer; import com.vaadin.util.ReflectTools; import elemental.json.Json; -import elemental.json.JsonArray; import elemental.json.JsonObject; import elemental.json.JsonValue; @@ -710,8 +713,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, /** * The server-side interface that controls Grid's selection state. + * SelectionModel should extend {@link AbstractGridExtension}. */ - public interface SelectionModel extends Serializable { + public interface SelectionModel extends Serializable, Extension { /** * Checks whether an item is selected or not. * @@ -730,6 +734,8 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, /** * Injects the current {@link Grid} instance into the SelectionModel. + * This method should usually call the extend method of + * {@link AbstractExtension}. *

    * Note: This method should not be called manually. * @@ -971,10 +977,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * A base class for SelectionModels that contains some of the logic that is * reusable. */ - public static abstract class AbstractSelectionModel implements - SelectionModel { + public static abstract class AbstractSelectionModel extends + AbstractGridExtension implements SelectionModel, DataGenerator { protected final LinkedHashSet selection = new LinkedHashSet(); - protected Grid grid = null; @Override public boolean isSelected(final Object itemId) { @@ -988,7 +993,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void setGrid(final Grid grid) { - this.grid = grid; + if (grid != null) { + extend(grid); + } } /** @@ -1002,7 +1009,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ protected void checkItemIdExists(Object itemId) throws IllegalArgumentException { - if (!grid.getContainerDataSource().containsId(itemId)) { + if (!getParentGrid().getContainerDataSource().containsId(itemId)) { throw new IllegalArgumentException("Given item id (" + itemId + ") does not exist in the container"); } @@ -1044,7 +1051,19 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, protected void fireSelectionEvent( final Collection oldSelection, final Collection newSelection) { - grid.fireSelectionEvent(oldSelection, newSelection); + getParentGrid().fireSelectionEvent(oldSelection, newSelection); + } + + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + if (isSelected(itemId)) { + rowData.put(GridState.JSONKEY_SELECTED, true); + } + } + + @Override + protected Object getItemId(String rowKey) { + return rowKey != null ? super.getItemId(rowKey) : null; } } @@ -1053,8 +1072,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ public static class SingleSelectionModel extends AbstractSelectionModel implements SelectionModel.Single { + + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + registerRpc(new SingleSelectionModelServerRpc() { + + @Override + public void select(String rowKey) { + SingleSelectionModel.this.select(getItemId(rowKey), false); + } + }); + } + @Override public boolean select(final Object itemId) { + return select(itemId, true); + } + + protected boolean select(final Object itemId, boolean refresh) { if (itemId == null) { return deselect(getSelectedRow()); } @@ -1066,7 +1102,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, if (modified) { final Collection deselected; if (selectedRow != null) { - deselectInternal(selectedRow, false); + deselectInternal(selectedRow, false, true); deselected = Collections.singleton(selectedRow); } else { deselected = Collections.emptySet(); @@ -1075,19 +1111,28 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, fireSelectionEvent(deselected, selection); } + if (refresh) { + refreshRow(itemId); + } + return modified; } private boolean deselect(final Object itemId) { - return deselectInternal(itemId, true); + return deselectInternal(itemId, true, true); } private boolean deselectInternal(final Object itemId, - boolean fireEventIfNeeded) { + boolean fireEventIfNeeded, boolean refresh) { final boolean modified = selection.remove(itemId); - if (fireEventIfNeeded && modified) { - fireSelectionEvent(Collections.singleton(itemId), - Collections.emptySet()); + if (modified) { + if (refresh) { + refreshRow(itemId); + } + if (fireEventIfNeeded) { + fireSelectionEvent(Collections.singleton(itemId), + Collections.emptySet()); + } } return modified; } @@ -1113,23 +1158,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public void setDeselectAllowed(boolean deselectAllowed) { - grid.getState().singleSelectDeselectAllowed = deselectAllowed; + getState().deselectAllowed = deselectAllowed; } @Override public boolean isDeselectAllowed() { - return grid.getState(false).singleSelectDeselectAllowed; + return getState().deselectAllowed; + } + + @Override + protected SingleSelectionModelState getState() { + return (SingleSelectionModelState) super.getState(); } } /** * A default implementation for a {@link SelectionModel.None} */ - public static class NoSelectionModel implements SelectionModel.None { - @Override - public void setGrid(final Grid grid) { - // NOOP, not needed for anything - } + public static class NoSelectionModel extends AbstractSelectionModel + implements SelectionModel.None { @Override public boolean isSelected(final Object itemId) { @@ -1167,7 +1214,40 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, private int selectionLimit = DEFAULT_MAX_SELECTIONS; - private boolean allSelected; + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + registerRpc(new MultiSelectionModelServerRpc() { + + @Override + public void select(List rowKeys) { + List items = new ArrayList(); + for (String rowKey : rowKeys) { + items.add(getItemId(rowKey)); + } + MultiSelectionModel.this.select(items, false); + } + + @Override + public void deselect(List rowKeys) { + List items = new ArrayList(); + for (String rowKey : rowKeys) { + items.add(getItemId(rowKey)); + } + MultiSelectionModel.this.deselect(items, false); + } + + @Override + public void selectAll() { + MultiSelectionModel.this.selectAll(false); + } + + @Override + public void deselectAll() { + MultiSelectionModel.this.deselectAll(false); + } + }); + } @Override public boolean select(final Object... itemIds) @@ -1190,6 +1270,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public boolean select(final Collection itemIds) throws IllegalArgumentException { + return select(itemIds, true); + } + + protected boolean select(final Collection itemIds, boolean refresh) { if (itemIds == null) { throw new IllegalArgumentException("itemIds may not be null"); } @@ -1217,6 +1301,12 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, updateAllSelectedState(); + if (refresh) { + for (Object itemId : itemIds) { + refreshRow(itemId); + } + } + return selectionWillChange; } @@ -1270,6 +1360,10 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, @Override public boolean deselect(final Collection itemIds) throws IllegalArgumentException { + return deselect(itemIds, true); + } + + protected boolean deselect(final Collection itemIds, boolean refresh) { if (itemIds == null) { throw new IllegalArgumentException("itemIds may not be null"); } @@ -1285,15 +1379,25 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, updateAllSelectedState(); + if (refresh) { + for (Object itemId : itemIds) { + refreshRow(itemId); + } + } + return hasCommonElements; } @Override public boolean selectAll() { + return selectAll(true); + } + + protected boolean selectAll(boolean refresh) { // select will fire the event - final Indexed container = grid.getContainerDataSource(); + final Indexed container = getParentGrid().getContainerDataSource(); if (container != null) { - return select(container.getItemIds()); + return select(container.getItemIds(), refresh); } else if (selection.isEmpty()) { return false; } else { @@ -1302,14 +1406,18 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * but I guess the only theoretically correct course of * action... */ - return deselectAll(); + return deselectAll(false); } } @Override public boolean deselectAll() { + return deselectAll(true); + } + + protected boolean deselectAll(boolean refresh) { // deselect will fire the event - return deselect(getSelectedRows()); + return deselect(getSelectedRows(), refresh); } /** @@ -1382,11 +1490,15 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } private void updateAllSelectedState() { - if (allSelected != selection.size() >= selectionLimit) { - allSelected = selection.size() >= selectionLimit; - grid.getRpcProxy(GridClientRpc.class).setSelectAll(allSelected); + if (getState().allSelected != selection.size() >= selectionLimit) { + getState().allSelected = selection.size() >= selectionLimit; } } + + @Override + protected MultiSelectionModelState getState() { + return (MultiSelectionModelState) super.getState(); + } } /** @@ -1571,7 +1683,7 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, * Grid rows. If a description is generated for a row, it is used for all * the cells in the row for which a {@link CellDescriptionGenerator cell * description} is not generated. - * + * * @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator) * * @since 7.6 @@ -3783,6 +3895,17 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, + " instead"); } } + + /** + * Resends the row data for given item id to the client. + * + * @since + * @param itemId + * row to refresh + */ + protected void refreshRow(Object itemId) { + getParentGrid().datasourceExtension.updateRowData(itemId); + } } /** @@ -3982,116 +4105,9 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, */ private void initGrid() { setSelectionMode(getDefaultSelectionMode()); - addSelectionListener(new SelectionListener() { - @Override - public void select(SelectionEvent event) { - if (applyingSelectionFromClient) { - /* - * Avoid sending changes back to the client if they - * originated from the client. Instead, the RPC handler is - * responsible for keeping track of the resulting selection - * state and notifying the client if it doens't match the - * expectation. - */ - return; - } - - /* - * The rows are pinned here to ensure that the client gets the - * correct key from server when the selected row is first - * loaded. - * - * Once the client has gotten info that it is supposed to select - * a row, it will pin the data from the client side as well and - * it will be unpinned once it gets deselected. Nothing on the - * server side should ever unpin anything from KeyMapper. - * Pinning is mostly a client feature and is only used when - * selecting something from the server side. - */ - for (Object addedItemId : event.getAdded()) { - if (!getKeyMapper().isPinned(addedItemId)) { - getKeyMapper().pin(addedItemId); - } - } - - getState().selectedKeys = getKeyMapper().getKeys( - getSelectedRows()); - } - }); registerRpc(new GridServerRpc() { - @Override - public void select(List selection) { - Collection receivedSelection = getKeyMapper() - .getItemIds(selection); - - applyingSelectionFromClient = true; - try { - SelectionModel selectionModel = getSelectionModel(); - if (selectionModel instanceof SelectionModel.Single - && selection.size() <= 1) { - Object select = null; - if (selection.size() == 1) { - select = getKeyMapper().getItemId(selection.get(0)); - } - ((SelectionModel.Single) selectionModel).select(select); - } else if (selectionModel instanceof SelectionModel.Multi) { - ((SelectionModel.Multi) selectionModel) - .setSelected(receivedSelection); - } else { - throw new IllegalStateException("SelectionModel " - + selectionModel.getClass().getSimpleName() - + " does not support selecting the given " - + selection.size() + " items."); - } - } finally { - applyingSelectionFromClient = false; - } - - Collection actualSelection = getSelectedRows(); - - // Make sure all selected rows are pinned - for (Object itemId : actualSelection) { - if (!getKeyMapper().isPinned(itemId)) { - getKeyMapper().pin(itemId); - } - } - - // Don't mark as dirty since this might be the expected state - getState(false).selectedKeys = getKeyMapper().getKeys( - actualSelection); - - JsonObject diffState = getUI().getConnectorTracker() - .getDiffState(Grid.this); - - final String diffstateKey = "selectedKeys"; - - assert diffState.hasKey(diffstateKey) : "Field name has changed"; - - if (receivedSelection.equals(actualSelection)) { - /* - * We ended up with the same selection state that the client - * sent us. There's nothing to send back to the client, just - * update the diffstate so subsequent changes will be - * detected. - */ - JsonArray diffSelected = Json.createArray(); - for (String rowKey : getState(false).selectedKeys) { - diffSelected.set(diffSelected.length(), rowKey); - } - diffState.put(diffstateKey, diffSelected); - } else { - /* - * Actual selection is not what the client expects. Make - * sure the client gets a state change event by clearing the - * diffstate and marking as dirty - */ - diffState.remove(diffstateKey); - markAsDirty(); - } - } - @Override public void sort(String[] columnIds, SortDirection[] directions, boolean userOriginated) { @@ -4120,13 +4136,6 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, } } - @Override - public void selectAll() { - assert getSelectionModel() instanceof SelectionModel.Multi : "Not a multi selection model!"; - - ((SelectionModel.Multi) getSelectionModel()).selectAll(); - } - @Override public void itemClick(String rowKey, String columnId, MouseEventDetails details) { @@ -5019,25 +5028,11 @@ public class Grid extends AbstractFocusable implements SelectionNotifier, if (this.selectionModel != selectionModel) { // this.selectionModel is null on init if (this.selectionModel != null) { - this.selectionModel.reset(); - this.selectionModel.setGrid(null); + this.selectionModel.remove(); } this.selectionModel = selectionModel; - this.selectionModel.setGrid(this); - this.selectionModel.reset(); - - if (selectionModel.getClass().equals(SingleSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.SINGLE; - } else if (selectionModel.getClass().equals( - MultiSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.MULTI; - } else if (selectionModel.getClass().equals(NoSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.NONE; - } else { - throw new UnsupportedOperationException("Grid currently " - + "supports only its own bundled selection models"); - } + selectionModel.setGrid(this); } } diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java index 28e50d9747..05965ea56c 100644 --- a/shared/src/com/vaadin/shared/data/DataProviderRpc.java +++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java @@ -20,7 +20,6 @@ import com.vaadin.shared.annotations.NoLayout; import com.vaadin.shared.communication.ClientRpc; import elemental.json.JsonArray; -import elemental.json.JsonObject; /** * RPC interface used for pushing container data to the client. @@ -94,13 +93,14 @@ public interface DataProviderRpc extends ClientRpc { public void resetDataAndSize(int size); /** - * Informs the client that a row has updated. The client-side DataSource - * will map the given data to correct index if it should be in the cache. + * Informs the client that rows have been updated. The client-side + * DataSource will map the given data to correct index if it should be in + * the cache. * * @since 7.6 - * @param row - * the updated row data + * @param rowArray + * array of updated rows */ @NoLayout - public void updateRowData(JsonObject row); + public void updateRowData(JsonArray rowArray); } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index 8711a757a1..ac1b1d5a78 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -55,15 +55,4 @@ public interface GridClientRpc extends ClientRpc { * Command client Grid to recalculate column widths. */ public void recalculateColumnWidths(); - - /** - * Informs the Grid that all items have been selected or not selected on the - * server side. - * - * @since 7.5.3 - * @param allSelected - * true to check the select all checkbox, - * false to uncheck it. - */ - public void setSelectAll(boolean allSelected); } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index a178ff63d0..8d64794b41 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -29,10 +29,6 @@ import com.vaadin.shared.data.sort.SortDirection; */ public interface GridServerRpc extends ServerRpc { - void select(List newSelection); - - void selectAll(); - void sort(String[] columnIds, SortDirection[] directions, boolean userOriginated); diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index 0d0a5d3e9f..e51b1a78a7 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -128,6 +128,13 @@ public class GridState extends TabIndexState { * */ public static final String JSONKEY_DETAILS_VISIBLE = "dv"; + /** + * The key that tells whether row is selected. + * + * @since + */ + public static final String JSONKEY_SELECTED = "s"; + /** * Columns in grid. */ @@ -153,14 +160,6 @@ public class GridState extends TabIndexState { @DelegateToWidget public HeightMode heightMode = HeightMode.CSS; - // instantiated just to avoid NPEs - public List selectedKeys = new ArrayList(); - - public SharedSelectionMode selectionMode; - - /** Whether single select mode can be cleared through the UI */ - public boolean singleSelectDeselectAllowed = true; - /** Keys of the currently sorted columns */ public String[] sortColumns = new String[0]; diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java new file mode 100644 index 0000000000..e7324552f4 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java @@ -0,0 +1,55 @@ +/* + * 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.shared.ui.grid.selection; + +import java.util.List; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * ServerRpc for MultiSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public interface MultiSelectionModelServerRpc extends ServerRpc { + + /** + * Select a list of rows based on their row keys on the server-side. + * + * @param rowKeys + * list of row keys to select + */ + public void select(List rowKeys); + + /** + * Deselect a list of rows based on their row keys on the server-side. + * + * @param rowKeys + * list of row keys to deselect + */ + public void deselect(List rowKeys); + + /** + * Selects all rows on the server-side. + */ + public void selectAll(); + + /** + * Deselects all rows on the server-side. + */ + public void deselectAll(); +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java new file mode 100644 index 0000000000..3ffd0d0f93 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java @@ -0,0 +1,31 @@ +/* + * 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.shared.ui.grid.selection; + +import com.vaadin.shared.communication.SharedState; + +/** + * SharedState object for MultiSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public class MultiSelectionModelState extends SharedState { + + /* Select All -checkbox status */ + public boolean allSelected; + +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java new file mode 100644 index 0000000000..3e2a8ec847 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java @@ -0,0 +1,35 @@ +/* + * 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.shared.ui.grid.selection; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * ServerRpc for SingleSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public interface SingleSelectionModelServerRpc extends ServerRpc { + + /** + * Selects a row on server-side. + * + * @param rowKey + * row key of selected row; {@code null} if deselect + */ + public void select(String rowKey); +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java new file mode 100644 index 0000000000..719b60b2ba --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java @@ -0,0 +1,30 @@ +/* + * 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.shared.ui.grid.selection; + +import com.vaadin.shared.communication.SharedState; + +/** + * SharedState object for SingleSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public class SingleSelectionModelState extends SharedState { + + /* Allow deselecting rows */ + public boolean deselectAllowed; +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java new file mode 100644 index 0000000000..008c24cdd3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java @@ -0,0 +1,37 @@ +/* + * 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.components.grid; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.widgetset.TestingWidgetSet; +import com.vaadin.ui.Grid.MultiSelectionModel; + +@Widgetset(TestingWidgetSet.NAME) +public class GridCustomSelectionModel extends AbstractTestUI { + + public static class MySelectionModel extends MultiSelectionModel { + } + + @Override + protected void setup(VaadinRequest request) { + PersonTestGrid grid = new PersonTestGrid(500); + grid.setSelectionModel(new MySelectionModel()); + addComponent(grid); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java new file mode 100644 index 0000000000..976e1e78fe --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java @@ -0,0 +1,58 @@ +/* + * 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.components.grid; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.elements.GridElement.GridCellElement; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.tb3.MultiBrowserTest; + +@TestCategory("grid") +public class GridCustomSelectionModelTest extends MultiBrowserTest { + + @Test + public void testCustomSelectionModel() { + setDebug(true); + openTestURL(); + + GridElement grid = $(GridElement.class).first(); + GridCellElement cell = grid.getCell(0, 0); + assertTrue("First column of Grid should not have an input element", + cell.findElements(By.className("input")).isEmpty()); + + assertFalse("Row should not be selected initially", grid.getRow(0) + .isSelected()); + + cell.click(5, 5); + assertTrue("Click should select row", grid.getRow(0).isSelected()); + cell.click(5, 5); + assertFalse("Click should deselect row", grid.getRow(0).isSelected()); + + grid.sendKeys(Keys.SPACE); + assertTrue("Space should select row", grid.getRow(0).isSelected()); + grid.sendKeys(Keys.SPACE); + assertFalse("Space should deselect row", grid.getRow(0).isSelected()); + + assertNoErrorNotifications(); + } +} diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java new file mode 100644 index 0000000000..81a9ab5bf1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java @@ -0,0 +1,61 @@ +/* + * 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.widgetset.client.grid; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.connectors.MultiSelectionModelConnector; +import com.vaadin.client.renderers.ComplexRenderer; +import com.vaadin.client.widget.grid.selection.ClickSelectHandler; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.ui.Connect; +import com.vaadin.tests.components.grid.GridCustomSelectionModel.MySelectionModel; + +import elemental.json.JsonObject; + +@Connect(MySelectionModel.class) +public class MySelectionModelConnector extends MultiSelectionModelConnector { + + private ClickSelectHandler handler; + + @Override + protected void extend(ServerConnector target) { + super.extend(target); + handler = new ClickSelectHandler(getGrid()); + } + + @Override + public void onUnregister() { + super.onUnregister(); + handler.removeHandler(); + handler = null; + } + + @Override + protected Multi createSelectionModel() { + return new MySelectionModel(); + } + + public class MySelectionModel extends MultiSelectionModel { + + @Override + protected ComplexRenderer createSelectionColumnRenderer( + Grid grid) { + // No Selection Column. + return null; + } + } +} -- cgit v1.2.3 From f3f3a74d5779b5ff0270700a998b1b62c60a45b9 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 3 Aug 2015 12:30:34 +0300 Subject: Support suspend timeout for long polling (#18550) Change-Id: I1dc35f060b255baff4d28a815414836d4852218b --- WebContent/WEB-INF/web.xml | 15 ++++++++ server/src/com/vaadin/server/Constants.java | 1 + .../vaadin/server/communication/PushHandler.java | 41 +++++++++++++++++++++- .../server/communication/PushRequestHandler.java | 25 +++++++++++-- .../tests/push/BasicPushLongPollingTest.java | 14 ++++++++ .../src/com/vaadin/tests/push/BasicPushTest.java | 8 ++--- .../src/com/vaadin/tests/tb3/AbstractTB3Test.java | 2 +- 7 files changed, 98 insertions(+), 8 deletions(-) (limited to 'WebContent') diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index 51ac907843..a9d3db30b3 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -115,6 +115,16 @@ true + + VaadinApplicationRunnerWithPushTimeout + com.vaadin.launcher.ApplicationRunnerServlet + + pushLongPollingSuspendTimeout + 10000 + + true + + IntegrationTest @@ -159,6 +169,11 @@ /run-push/* + + VaadinApplicationRunnerWithPushTimeout + /run-push-timeout/* + + VaadinApplicationRunnerWithJSR356 /run-jsr356/* diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java index 5a0d852299..77a1a3134e 100644 --- a/server/src/com/vaadin/server/Constants.java +++ b/server/src/com/vaadin/server/Constants.java @@ -137,6 +137,7 @@ public interface Constants { static final String SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING = "legacyPropertyToString"; static final String SERVLET_PARAMETER_SYNC_ID_CHECK = "syncIdCheck"; static final String SERVLET_PARAMETER_SENDURLSASPARAMETERS = "sendUrlsAsParameters"; + static final String SERVLET_PARAMETER_PUSH_SUSPEND_TIMEOUT_LONGPOLLING = "pushLongPollingSuspendTimeout"; // Configurable parameter names static final String PARAMETER_VAADIN_RESOURCES = "Resources"; diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java index 01077c3f86..994415a0b4 100644 --- a/server/src/com/vaadin/server/communication/PushHandler.java +++ b/server/src/com/vaadin/server/communication/PushHandler.java @@ -55,6 +55,8 @@ import elemental.json.JsonException; */ public class PushHandler { + private int longPollingSuspendTimeout = -1; + /** * Callback interface used internally to process an event with the * corresponding UI properly locked. @@ -107,7 +109,7 @@ public class PushHandler { return; } - resource.suspend(); + suspend(resource); AtmospherePushConnection connection = getConnectionForUI(ui); assert (connection != null); @@ -173,6 +175,21 @@ public class PushHandler { this.service = service; } + /** + * Suspends the given resource + * + * @since + * @param resource + * the resource to suspend + */ + protected void suspend(AtmosphereResource resource) { + if (resource.transport() == TRANSPORT.LONG_POLLING) { + resource.suspend(getLongPollingSuspendTimeout()); + } else { + resource.suspend(-1); + } + } + /** * Find the UI for the atmosphere resource, lock it and invoke the callback. * @@ -493,4 +510,26 @@ public class PushHandler { resource.transport() == TRANSPORT.WEBSOCKET); } + /** + * Sets the timeout used for suspend calls when using long polling. + * + * If you are using a proxy with a defined idle timeout, set the suspend + * timeout to a value smaller than the proxy timeout so that the server is + * aware of a reconnect taking place. + * + * @param suspendTimeout + * the timeout to use for suspended AtmosphereResources + */ + public void setLongPollingSuspendTimeout(int longPollingSuspendTimeout) { + this.longPollingSuspendTimeout = longPollingSuspendTimeout; + } + + /** + * Gets the timeout used for suspend calls when using long polling. + * + * @return the timeout to use for suspended AtmosphereResources + */ + public int getLongPollingSuspendTimeout() { + return longPollingSuspendTimeout; + } } diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java index c01c74e5cd..c44fcd9ef3 100644 --- a/server/src/com/vaadin/server/communication/PushRequestHandler.java +++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java @@ -77,7 +77,7 @@ public class PushRequestHandler implements RequestHandler, final ServletConfig vaadinServletConfig = service.getServlet() .getServletConfig(); - pushHandler = new PushHandler(service); + pushHandler = createPushHandler(service); atmosphere = getPreInitializedAtmosphere(vaadinServletConfig); if (atmosphere == null) { @@ -100,7 +100,12 @@ public class PushRequestHandler implements RequestHandler, "Using pre-initialized Atmosphere for servlet " + vaadinServletConfig.getServletName()); } - + pushHandler + .setLongPollingSuspendTimeout(atmosphere + .getAtmosphereConfig() + .getInitParameter( + com.vaadin.server.Constants.SERVLET_PARAMETER_PUSH_SUSPEND_TIMEOUT_LONGPOLLING, + -1)); for (AtmosphereHandlerWrapper handlerWrapper : atmosphere .getAtmosphereHandlers().values()) { AtmosphereHandler handler = handlerWrapper.atmosphereHandler; @@ -113,6 +118,22 @@ public class PushRequestHandler implements RequestHandler, } } + /** + * Creates a push handler for this request handler. + *

    + * Create your own request handler and override this method if you want to + * customize the {@link PushHandler}, e.g. to dynamically decide the suspend + * timeout. + * + * @since + * @param service + * the vaadin service + * @return the push handler to use for this service + */ + protected PushHandler createPushHandler(VaadinServletService service) { + return new PushHandler(service); + } + private static final Logger getLogger() { return Logger.getLogger(PushRequestHandler.class.getName()); } diff --git a/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java b/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java index b404747c80..a060d5a57a 100644 --- a/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java +++ b/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java @@ -15,5 +15,19 @@ */ package com.vaadin.tests.push; +import org.junit.Test; + public class BasicPushLongPollingTest extends BasicPushTest { + + @Test + public void pushAfterServerTimeout() throws InterruptedException { + getDriver().get( + getTestUrl().replace("/run/", "/run-push-timeout/") + + "?debug=push"); + sleep(11000); // Wait for server timeout (10s) + + getServerCounterStartButton().click(); + waitUntilServerCounterChanges(); + } + } diff --git a/uitest/src/com/vaadin/tests/push/BasicPushTest.java b/uitest/src/com/vaadin/tests/push/BasicPushTest.java index 5bac54f0f7..f176008eb0 100644 --- a/uitest/src/com/vaadin/tests/push/BasicPushTest.java +++ b/uitest/src/com/vaadin/tests/push/BasicPushTest.java @@ -57,11 +57,11 @@ public abstract class BasicPushTest extends MultiBrowserTest { return Integer.parseInt(clientCounterElem.getText()); } - private WebElement getIncrementButton() { + protected WebElement getIncrementButton() { return getIncrementButton(this); } - private WebElement getServerCounterStartButton() { + protected WebElement getServerCounterStartButton() { return getServerCounterStartButton(this); } @@ -83,7 +83,7 @@ public abstract class BasicPushTest extends MultiBrowserTest { return t.vaadinElementById(BasicPush.INCREMENT_BUTTON_ID); } - private void waitUntilClientCounterChanges(final int expectedValue) { + protected void waitUntilClientCounterChanges(final int expectedValue) { waitUntil(new ExpectedCondition() { @Override @@ -93,7 +93,7 @@ public abstract class BasicPushTest extends MultiBrowserTest { }, 10); } - private void waitUntilServerCounterChanges() { + protected void waitUntilServerCounterChanges() { final int counter = BasicPushTest.getServerCounter(this); waitUntil(new ExpectedCondition() { diff --git a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java index 2e3854cb2b..d76cd616b1 100644 --- a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java +++ b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java @@ -381,7 +381,7 @@ public abstract class AbstractTB3Test extends ParallelTest { * @return */ public WebElement vaadinElementById(String id) { - return driver.findElement(vaadinLocatorById(id)); + return driver.findElement(By.id(id)); } /** -- cgit v1.2.3