Browse Source

Merge branch 'master' into fix11576

fix11576
Tatu Lund 3 years ago
parent
commit
60d4551b4d
No account linked to committer's email address
100 changed files with 1760 additions and 520 deletions
  1. 186
    0
      README-DEV.md
  2. 12
    84
      README.md
  3. 1
    1
      all/pom.xml
  4. 6
    8
      all/src/main/templates/release-notes.html
  5. 1
    1
      bom/pom.xml
  6. 1
    1
      client-compiled/pom.xml
  7. 1
    1
      client-compiler/pom.xml
  8. 1
    1
      client/pom.xml
  9. 24
    7
      client/src/main/java/com/vaadin/client/Util.java
  10. 22
    0
      client/src/main/java/com/vaadin/client/WidgetUtil.java
  11. 1
    1
      client/src/main/java/com/vaadin/client/communication/DefaultConnectionStateHandler.java
  12. 56
    7
      client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
  13. 39
    3
      client/src/main/java/com/vaadin/client/ui/ShortcutActionHandler.java
  14. 34
    10
      client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java
  15. 3
    0
      client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java
  16. 75
    27
      client/src/main/java/com/vaadin/client/ui/VComboBox.java
  17. 2
    0
      client/src/main/java/com/vaadin/client/ui/VContextMenu.java
  18. 2
    2
      client/src/main/java/com/vaadin/client/ui/VRichTextArea.java
  19. 65
    8
      client/src/main/java/com/vaadin/client/ui/VUpload.java
  20. 10
    4
      client/src/main/java/com/vaadin/client/ui/VWindow.java
  21. 6
    1
      client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java
  22. 10
    0
      client/src/main/java/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java
  23. 25
    1
      client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java
  24. 24
    25
      client/src/main/java/com/vaadin/client/ui/upload/UploadConnector.java
  25. 8
    2
      client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java
  26. 70
    11
      client/src/main/java/com/vaadin/client/widgets/Escalator.java
  27. 180
    84
      client/src/main/java/com/vaadin/client/widgets/Grid.java
  28. 1
    1
      compatibility-client-compiled/pom.xml
  29. 1
    1
      compatibility-client/pom.xml
  30. 2
    17
      compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java
  31. 14
    5
      compatibility-client/src/main/java/com/vaadin/v7/client/ui/VScrollTable.java
  32. 3
    0
      compatibility-client/src/main/java/com/vaadin/v7/client/ui/VTextualDate.java
  33. 1
    0
      compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Escalator.java
  34. 46
    24
      compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Grid.java
  35. 1
    1
      compatibility-server-gae/pom.xml
  36. 1
    1
      compatibility-server/pom.xml
  37. 3
    1
      compatibility-server/src/main/java/com/vaadin/v7/ui/ComboBox.java
  38. 2
    0
      compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java
  39. 35
    1
      compatibility-server/src/main/java/com/vaadin/v7/ui/Table.java
  40. 1
    1
      compatibility-shared/pom.xml
  41. 1
    1
      compatibility-themes/pom.xml
  42. 1
    0
      compatibility-themes/src/main/themes/VAADIN/themes/base/table/table.scss
  43. 1
    0
      documentation/articles/FindingTheCurrentUIAndPageAndVaadinSession.asciidoc
  44. 9
    2
      documentation/articles/LettingTheUserDownloadAFile.asciidoc
  45. 7
    0
      documentation/components/components-grid.asciidoc
  46. 13
    0
      documentation/datamodel/datamodel-forms.asciidoc
  47. 1
    1
      liferay-integration/pom.xml
  48. 1
    1
      liferay/pom.xml
  49. 1
    1
      osgi-integration/pom.xml
  50. 16
    2
      pom.xml
  51. 1
    1
      push/pom.xml
  52. 1
    1
      server/bnd.bnd
  53. 1
    1
      server/pom.xml
  54. 5
    2
      server/src/main/java/com/vaadin/data/BeanValidationBinder.java
  55. 135
    19
      server/src/main/java/com/vaadin/data/Binder.java
  56. 2
    1
      server/src/main/java/com/vaadin/data/RequiredFieldConfigurator.java
  57. 0
    5
      server/src/main/java/com/vaadin/data/ValueContext.java
  58. 2
    2
      server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java
  59. 13
    2
      server/src/main/java/com/vaadin/data/provider/TreeDataProvider.java
  60. 5
    0
      server/src/main/java/com/vaadin/navigator/Navigator.java
  61. 2
    4
      server/src/main/java/com/vaadin/server/BootstrapHandler.java
  62. 14
    0
      server/src/main/java/com/vaadin/server/Sizeable.java
  63. 18
    6
      server/src/main/java/com/vaadin/server/VaadinService.java
  64. 6
    7
      server/src/main/java/com/vaadin/server/communication/HeartbeatHandler.java
  65. 6
    9
      server/src/main/java/com/vaadin/server/communication/PushHandler.java
  66. 20
    2
      server/src/main/java/com/vaadin/ui/AbstractComponent.java
  67. 119
    21
      server/src/main/java/com/vaadin/ui/AbstractDateField.java
  68. 6
    4
      server/src/main/java/com/vaadin/ui/ConnectorTracker.java
  69. 9
    3
      server/src/main/java/com/vaadin/ui/Grid.java
  70. 12
    0
      server/src/main/java/com/vaadin/ui/Tree.java
  71. 5
    4
      server/src/main/java/com/vaadin/ui/UI.java
  72. 60
    35
      server/src/main/java/com/vaadin/ui/Upload.java
  73. 5
    1
      server/src/main/java/com/vaadin/ui/components/grid/GridRowDragger.java
  74. 2
    2
      server/src/main/java/com/vaadin/ui/declarative/Design.java
  75. 5
    0
      server/src/main/java/com/vaadin/ui/themes/ValoTheme.java
  76. 37
    5
      server/src/main/java/com/vaadin/util/TimeZoneUtil.java
  77. 77
    2
      server/src/test/java/com/vaadin/data/BinderTest.java
  78. 49
    4
      server/src/test/java/com/vaadin/data/provider/TreeDataProviderTest.java
  79. 7
    5
      server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithDataTest.java
  80. 20
    0
      server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerOneGridTest.java
  81. 11
    2
      server/src/test/java/com/vaadin/ui/DateFieldTestCase.java
  82. 1
    1
      shared/pom.xml
  83. 44
    4
      shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Criterion.java
  84. 4
    1
      shared/src/main/java/com/vaadin/shared/ui/upload/UploadClientRpc.java
  85. 3
    0
      shared/src/main/java/com/vaadin/shared/ui/upload/UploadServerRpc.java
  86. 13
    0
      shared/src/main/java/com/vaadin/shared/ui/upload/UploadState.java
  87. 1
    1
      test/addon-using-init-param-widget-set/pom.xml
  88. 1
    1
      test/addon-using-no-defined-widget-set/pom.xml
  89. 1
    1
      test/addon-using-own-widget-set/pom.xml
  90. 1
    1
      test/bean-api-validation/pom.xml
  91. 1
    1
      test/bean-impl-validation/pom.xml
  92. 1
    1
      test/cdi/pom.xml
  93. 1
    1
      test/default-widget-set/pom.xml
  94. 1
    1
      test/dependency-rewrite-addon/pom.xml
  95. 1
    1
      test/dependency-rewrite/pom.xml
  96. 1
    1
      test/own-widget-set/pom.xml
  97. 1
    1
      test/pom.xml
  98. 1
    1
      test/servlet-containers/generic-tests/pom.xml
  99. 1
    1
      test/servlet-containers/generic-ui/pom.xml
  100. 0
    0
      test/servlet-containers/generic/pom.xml

+ 186
- 0
README-DEV.md View File

@@ -0,0 +1,186 @@
# Development Instructions

To contribute, first refer to [Contributing Code](https://github.com/vaadin/framework/blob/master/CONTRIBUTING.md)
for general instructions and requirements for contributing code to the Vaadin framework.

We appreciate all contributors and want to make submitting changes as easy as possible. If you find any mistakes, unclear parts or out-of-date instructions, please let us know by submitting an issue or a pull request.

#### Table of Contents
1. [Building a package](#building-a-package)
1. [About committing changes](#about-committing-changes)
1. [Eclipse quick setup](#eclipse-quick-setup)
1. [IntelliJ IDEA quick setup](#intellij-idea-quick-setup)

## Building a package

The distribution files can be built by running the standard Maven goal `mvn install` in the project root.

## About committing changes

Despite our best efforts the formatting options aren't always entirely consistent between different development environments, and sometimes we miss inconsistent formatting during code review. When you commit your changes for a pull request, try to make sure that the commit _only contains changes that are relevant to your patch,_ or at least closely affiliated with the relevant changes. Random formatting changes all over the changed file(s) make it difficult to grasp the main purpose of your patch.

## Eclipse quick setup

For IntelliJ IDEA users, see [IntelliJ IDEA Quick Setup](#intellij-idea-quick-setup).

1. Decide were you would like your Eclipse workspace to be located.
* This project contains multiple modules and uses configurations that might clash with your existing projects, using a separate workspace is recommended.
* Eclipse Oxygen is recommended, different versions may treat formatting rules differently.
* If you are using Windows, you may wish to keep the workspace path reasonably short (e.g. `C:\dev\<workspaceName>`) to avoid problems with too long file paths.
1. Start Eclipse with your chosen workspace and set up [workspace preferences](#workspace-preferences).
1. Clone the repository within your selected workspace using Eclipse's clone wizard, using your favorite Git tool, or in command-line running
<code>git clone https://github.com/vaadin/framework.git</code> command.
* Eclipse's clone wizard can be found within Git perspective (*Window* -> *Perspectives* -> *Open Perspective* -> *Git*). _Only_ clone the project at this stage, do not let the clone wizard import projects as well.
* If using Windows, you might also want to add these Git settings: `core.autocrlf=false` and `core.fileMode=false`. You can do this in Eclipse by right-clicking the repository in Git perspective, clicking *Properties*, then *Add Entry...* and using key `core.autocrlf` and value `false` etc.
* If too long file paths become a problem you may also need `core.longpaths=true`.
1. Import the project into Eclipse as a Maven project. Use *File* -> *Import* -> *Maven* -> *Existing Maven Projects*.
1. Select the *framework* folder (where you cloned the project).
* It is not necessary to import all the modules, but it is recommended to include at least the root module, `vaadin-uitest` module, and any modules you may wish to make changes to. You can import more modules when needed by repeating these last steps.
1. Click “Finish” to complete the import of Vaadin Framework.

### Workspace preferences

The following preferences need to be set to keep the project consistent. You need to do this especially to be able to contribute changes to the project.

#### General

1. Open *Window* -> *Preferences* (Windows) or *Eclipse* -> *Preferences* (Mac)
1. Go to *General* -> *Workspace*
- Set *Text file encoding* to *UTF-8*
- Set *New text file line delimiter* to *Unix*
1. Go to *XML* -> *XML Files* -> *Editor*
- Ensure the settings are follows:
- Line width: 72
- Format comments: true
- Join lines: true
- Insert whitespace before closing empty end-tags: true
- Indent-using spaces: true
- Indentation size: 4
1. Go to *Java* -> *Compiler* -> *Errors*
- Switch *Serializable class without serialVersionUID* to *Ignore*
1. Go to *Java* -> *Installed JREs*
- Select a Java 8 JDK as the default

#### Configuration files

1. Open *Window* -> *Preferences* (Windows) or *Eclipse* -> *Preferences* (Mac)
1. Go to *Java* -> *Code Style* -> *Clean Up*
- Import /eclipse/VaadinCleanup.xml
1. Go to *Java* -> *Code Style* -> *Formatter*
- Import /eclipse/VaadinJavaConventions.xml

#### Save actions

1. Open *Window* -> *Preferences* (Windows) or *Eclipse* -> *Preferences* (Mac)
1. Go to *Java* -> *Editor* -> *Save Actions*
- Check *Perform the selected actions on save*
- Check *Format source code*
- Select *Format edited lines*
- Check *Organize imports*
- Check *Additional actions*
- Click *Configure*
1. In tab *Code Organizing*
- Check *Remove trailing whitespace*
- Select *All lines*
- Uncheck everything else
1. In tab *Code Style*
- Check *Use blocks in if/while/for/do statements*
- Select *Always*
- Uncheck everything else
1. In tab *Member Accesses*
- Check *Use 'this' qualifier for field accesses*
- Select *Only if necessary*
- Check *Use 'this' qualifier for method accesses*
- Select *Only if necessary*
- Uncheck everything else
1. In tab *Missing Code*
- Check *Add missing Annotations*
- Check *'@Override'*
- Check *Implementations of interface methods (1.6 or higher)*
- Check *'@Deprecated'*
1. In tab *Unnecessary Code*
- Check *Remove unused imports* and *Remove unnecessary casts*, uncheck everything else.
1. Click *OK*

After that is done, you should have 9 of 28 save actions activated and listed as such:

* Remove 'this' qualifier for non static field accesses
* Remove 'this' qualifier for non static method accesses
* Convert control statement bodies to block
* Remove unused imports
* Add missing '@Override' annotations
* Add missing '@Override' annotations to implementations of interface methods
* Add missing '@Deprecated' annotations
* Remove unnecessary casts
* Remove trailing white spaces on all lines

### Getting started

Run <code>install</code> maven goal for the project root to get started.
In Eclipse this is done by right-clicking on the project root in Project Explorer and choosing *Run As* -> *Maven Build...*. If you choose to skip tests you may need to run the <code>install</code> maven goal for `vaadin-uitest` project separately.
* Note that the first compilation takes a while to finish as Maven downloads dependencies used in the projects.
* In some Windows environments the compilation doesn't respect the `core.autocrlf=false` and the workspace preferences listed in the previous section, and running <code>install</code> converts all line endings from six core projects (root, `vaadin-client`, `vaadin-server`, `vaadin-shared`, `vaadin-testbench-api`, `vaadin-uitest`) to Windows-style. As a quick-and-dirty workaround you can change the line endings back through *File* -> *Convert Line Delimiters To* -> *Unix* and comment out references to plugins `net.revelc.code.formatter` and `com.github.dantwining.whitespace-maven-plugin` from each affected module's pom.xml (root project references them twice) to prevent it from happening again. *Do not* include those changes or any files with Windows-style line endings in any pull request. Because you consequently lose the formatting benefits of those plugins, you also need to be more careful about not including irrelevant formatting changes in your commits.

Now the project should compile without further configuration.

### Compiling the default widgetset and themes

* Compile the default widgetset by running <code>install</code> maven goal in `vaadin-client-compiled` module root.
In Eclipse this is done by right clicking on `vaadin-client-compiled` project in Project Explorer and choosing *Run As* -> *Maven Build...*.
You don't need to do this separately if you have already run <code>install</code> for the root project after your latest changes.
* Compile the default themes by running <code>install</code> maven goal in `vaadin-themes` module root.
In Eclipse this is done by right clicking on `vaadin-themes` project in Project Explorer and choosing *Run As* -> *Maven Build...*.
You don't need to do this separately if you have already run <code>install</code> for the root project after your latest changes.

### Running a UI test

#### Using DevelopmentServerLauncher (recommended)
1. In a Project Explorer navigate to *vaadin-uitest/src/main/java/com/vaadin/launcher*
1. Right-click file DevelopmentServerLauncher.java
1. Open *Run As* -> *Java Application*
1. Open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)

#### Using Jetty
1. In a Project Explorer right-click *vaadin-uitest*
1. Open *Run As* -> *Maven build...*
1. Type in <code>jetty:run-exploded</code> into *Goals* and click *Run*
1. Open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)

For full instructions please visit [README-TESTS.md](README-TESTS.md).

## IntelliJ IDEA quick setup

For Eclipse users, see [Eclipse Quick Setup](#eclipse-quick-setup).

1. Install and run IDEA. Ultimate Edition is better but Community Edition should also work.
1. Ensure if Git and Maven plugins are installed, properly configured and enabled.
1. Clone the repository, using menu VCS -> Checkout from Version Control -> Git -> Git Repository URL -> https://github.com/vaadin/framework.git.
When the repository is cloned, do **NOT** open it as a project.
* If you are using Windows, you may wish to keep the workspace path reasonably short (e.g. `C:\dev\<workspaceName>`) to avoid problems with too long file paths.
1. Open cloned repository as a maven object. Use File -> Open and choose root _pom.xml_ file
1. Have a coffee break while IDEA is loading dependencies and indexing the project
1. Run Maven targets <code>clean</code> and <code>install</code> using *Maven Projects* tool window to compile the whole project

Unfortunately there is no easy way to replicate [Eclipse workspace preferences](#workspace-preferences) in their entirety in IDEA, but you can use [Eclipse Code Formatter plugin](https://plugins.jetbrains.com/plugin/6546-eclipse-code-formatter) to import /eclipse/VaadinCleanup.xml and /eclipse/VaadinJavaConventions.xml as a starting point.

### Running a specific UI test

1. Open *Maven Projects*
1. Open *vaadin-uitest* -> *Plugins* -> *jetty* -> *jetty:run-exploded*
1. Open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)

For full instructions please visit [README-TESTS.md](README-TESTS.md).

### Running a development server

1. Open *Run* menu and click *Edit Configurations*
1. Click green ***+*** sign at top left corner, select *Maven* from popup
1. In the run configuration page, set any name for the configuration, select *vaadin-uitest* project folder as *Working directory*
1. Type <code>exec:exec@run-development-server</code> into *Command line* and save the configuration
1. Run the configuration and open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)

### Running a development server in a debug mode

1. Type <code>exec:exec@debug-development-server</code> into *Command line* and save the configuration
1. In the same dialog, create new "Remote" debug configuration, using *localhost* and *Port 5005*
1. Start both configurations and open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)

+ 12
- 84
README.md View File

@@ -2,97 +2,25 @@

# Vaadin Framework

*[Vaadin Framework](https://vaadin.com/framework) allows you to build modern web apps efficiently in plain Java, without touching low level web technologies.*
*[Vaadin](https://vaadin.com) allows you to build modern web apps efficiently in plain Java, without touching low level web technologies.*

For instructions about _using_ Vaadin to develop applications, please refer to [Vaadin tutorial](https://vaadin.com/docs/-/part/framework/tutorial.html) and other [documentation](https://vaadin.com/docs/).
This repository contains source code and issue tracking for Vaadin 8 and Vaadin 7, both of which use GWT as the base of client-side implementations. You can find source code and issue tracking for newer, web component based Vaadin versions in [vaadin/platform](https://github.com/vaadin/platform).

To contribute, first refer to [Contributing Code](https://github.com/vaadin/framework/blob/master/CONTRIBUTING.md)
for general instructions and requirements for contributing code to the Vaadin framework.

Instructions on how to set up a working environment for developing the Vaadin Framework follow below.

## Building a package

The distribution files can be built by running the standard Maven goal `mvn install` in the project root.

## Eclipse Quick Setup

1. Run
<code>git clone https://github.com/vaadin/framework.git</code>
command or clone the repository your favorite Git tool.
If using Windows, you might want to add these Git settings: `core.autocrlf=false`, `core.fileMode=false` and `core.longpaths=true`.
1. Run <code>mvn install</code> in the project root.
Note that the first compilation takes a while to finish as maven downloads dependencies used in the projects.
1. Start Eclipse with the workspace you would like to use. It is usually a good idea to use the parent folder of the Git repository as the workspace folder.
1. Import the project into Eclipse as a maven project. Use *File* -> *Import* -> *Maven* -> *Existing Maven Projects*.
1. Select the *framework* folder (where you cloned the project)
1. Click “Finish” to complete the import of Vaadin Framework

Now the project should compile without further configuration.

### Compiling the Default Widget Set and Themes

* Compile the default widgetset by running <code>install</code> maven goal in `vaadin-client-compiled` module root.
In Eclipse this is done by right clicking on vaadin-client-compiled project it and choosing *Run As* -> *Maven Build...*.
* Compile the default themes by running <code>install</code> maven goal in `vaadin-themes` module root.
In Eclipse this is done by right clicking on vaadin-themes project it and choosing *Run As* -> *Maven Build...*.

### Set up extra workspace preferences
Vaadin 8 includes Vaadin 7 compatibility classes and is supported until February 21, 2022 (extended support will be available for ten years after that).

The following preferences need to be set to keep the project consistent. You need to do this especially to be able to contribute changes to the project.
Vaadin 7 support has already ended, [extended support](https://vaadin.com/support/vaadin-7-extended-maintenance) is available until February 2029.

1. Open *Window* -> *Preferences* (Windows) or *Eclipse* -> *Preferences* (Mac)
1. Go to *General* -> *Workspace*
1. Set *Text file encoding* to *UTF-8*
1. Set *New text file line delimiter* to *Unix*
1. Go to XML -> XML Files -> Editor
1. Ensure the settings are follows:
## Using Vaadin 8 to develop applications

* Line width: 72
* Format comments: true
* Join lines: true
* Insert whitespace before closing empty end-tags: true
* Indent-using spaces: true
* Indentation size: 4
Please refer to [Vaadin tutorial](https://vaadin.com/docs/v8/framework/tutorial.html) and other [documentation](https://vaadin.com/docs/v8/index.html).

For known issues within Vaadin framework, see [Issue Tracker](https://github.com/vaadin/framework/issues). Comment or react to an existing issue to mark your interest in resolving it. If you don't find an existing report of an issue you are experiencing, [submit a new issue](https://github.com/vaadin/framework/issues/new/choose).

### Running a UI test
## Contributing

1. In a Project Explorer right-click *vaadin-uitest*
1. Open *Run As* -> *Maven build...*
1. Type in <code>jetty:run-exploded</code> into *Goals* and click *Run*
1. Open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)

For full instructions please visit [README-TESTS.md](README-TESTS.md).

## Setting up IntelliJ IDEA to Develop Vaadin Framework 8

1. Install and run IDEA. Ultimate Edition is better but Community Edition should also work.
1. Ensure if Git and Maven plugins are installed, properly configured and enabled.
1. Clone the repository, using menu VCS -> Checkout from Version Control -> Git -> Git Repository URL -> https://github.com/vaadin/framework.git.
When the repository is cloned, do **NOT** open it as a project.
1. Open cloned repository as a maven object. Use File -> Open and choose root _pom.xml_ file
1. Have a coffee break while IDEA is loading dependencies and indexing the project
1. Run Maven targets <code>clean</code> and <code>install</code> using *Maven Projects* tool window to compile the whole project

### Running a specific UI test

1. Open *Maven Projects*
1. Open *vaadin-uitest* -> *Plugins* -> *jetty* -> *jetty:run-exploded*
1. Open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)

For full instructions please visit [README-TESTS.md](README-TESTS.md).

### Running a Development Server

1. Open *Run* menu and click *Edit Configurations*
1. Click green ***+*** sign at top left corner, select *Maven* from popup
1. In the run configuration page, set any name for the configuration, select *vaadin-uitest* project folder as *Working directory*
1. Type <code>exec:exec@run-development-server</code> into *Command line* and save the configuration
1. Run the configuration and open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)
To contribute, first refer to [Contributing Code](CONTRIBUTING.md)
for general instructions and requirements for contributing code to the Vaadin framework.

### Running a Development Server in a debug mode
For instructions on how to set up a working environment for developing the Vaadin framework, please visit [Development Instructions](README-DEV.md). Pay special attention to workspace preferences.

1. Type <code>exec:exec@debug-development-server</code> into *Command line* and save the configuration
1. In the same dialog, create new "Remote" debug configuration, using *localhost* and *Port 5005*
1. Start both configurations and open URL [http://localhost:8888/run/&lt;testUI&gt;](http://localhost:8888/run/<testUI>)
We appreciate all contributors and want to make submitting changes as easy as possible. If you find any mistakes, unclear parts or out-of-date instructions, please let us know by submitting an issue or a pull request.

+ 1
- 1
all/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-all</artifactId>
<name>vaadin-all</name>

+ 6
- 8
all/src/main/templates/release-notes.html View File

@@ -83,28 +83,26 @@
enhancements. Below is a list of the most notable changes:</p>

<ul>
<li><tt></tt></li>
<li><tt></tt></li>
<li></li>
<li></li>
</ul>

</p>

<p>
For enhancements introduced in Vaadin Framework 8.9, see the <a
href="http://vaadin.com/download/release/8.9/8.9.0/release-notes.html">Release
Notes for Vaadin Framework 8.9.0</a>.
For enhancements introduced in Vaadin Framework 8.11, see the <a
href="http://vaadin.com/download/release/8.11/8.11.0/release-notes.html">Release
Notes for Vaadin Framework 8.11.0</a>.
For migrating from previous framework versions, see <a href="#incompatible">the list of incompatible changes</a> and <a href="#migrating">how to migrate
to Vaadin Framework 8</a>.
</p>

<h2 id="incompatible">No Incompatible or Behavior-altering Changes in @version-minor@</h2>
<h2 id="incompatible">Incompatible or Behavior-altering Changes in @version-minor@</h2>

<ul>
<li></li>
</ul>

<h2>For incompatible or behavior-altering changes in 8.9, please see <a href="https://vaadin.com/download/release/8.9/8.9.0/release-notes.html#incompatible">8.9 release notes</a></h2>
<h2>For incompatible or behavior-altering changes in 8.11, please see <a href="https://vaadin.com/download/release/8.11/8.11.0/release-notes.html#incompatible">8.11 release notes</a></h2>

<h3 id="knownissues">Known Issues and Limitations</h3>
<ul>

+ 1
- 1
bom/pom.xml View File

@@ -11,7 +11,7 @@
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<packaging>pom</packaging>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
<name>Vaadin Framework (Bill of Materials)</name>
<description>Vaadin Framework (Bill of Materials)</description>
<url>http://vaadin.com</url>

+ 1
- 1
client-compiled/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<!-- Needed by a plugin in release build -->
<groupId>com.vaadin</groupId>

+ 1
- 1
client-compiler/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-client-compiler</artifactId>
<name>vaadin-client-compiler</name>

+ 1
- 1
client/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<!-- Needed by a plugin in release build -->
<groupId>com.vaadin</groupId>

+ 24
- 7
client/src/main/java/com/vaadin/client/Util.java View File

@@ -579,15 +579,10 @@ public class Util {
}

if (connector != null) {
// check that inside the rootElement
while (browseElement != null && browseElement != rootElement) {
browseElement = browseElement.getParentElement();
}
if (browseElement != rootElement) {
return null;
} else {
if (isConnectedToParent(browseElement, rootElement)) {
return connector;
}
return null;
}

browseElement = browseElement.getParentElement();
@@ -606,6 +601,28 @@ public class Util {
}
}

private static boolean isConnectedToParent(Element element,
Element rootElement) {
Element browseElement = element;
// check if inside the rootElement
while (browseElement != null && browseElement != rootElement) {
browseElement = browseElement.getParentElement();
}
if (browseElement == rootElement) {
return true;
}
// Not inside the root, possibly inside a VOverlay such as
// VWindow instead.
@SuppressWarnings("deprecation")
VOverlay overlay = WidgetUtil.findWidget(element, VOverlay.class,
false);
if (overlay != null && overlay.getOwner() != null) {
browseElement = overlay.getOwner().getElement();
return isConnectedToParent(browseElement, rootElement);
}
return false;
}

/**
* Will (attempt) to focus the given DOM Element.
*

+ 22
- 0
client/src/main/java/com/vaadin/client/WidgetUtil.java View File

@@ -42,6 +42,7 @@ import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.shared.ui.ErrorLevel;
import com.vaadin.shared.util.SharedUtil;
@@ -1962,4 +1963,25 @@ public class WidgetUtil {
return indicator;
}
}

public static void disableBrowserAutocomplete(TextBox textBox) {
/*-
* Stop the browser from showing its own suggestion popup.
*
* Using an invalid value instead of "off" as suggested by
* https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
*
* Leaving the non-standard Safari options autocapitalize and
* autocorrect untouched since those do not interfere in the same
* way, and they might be useful in a combo box where new items are
* allowed.
*/
if (BrowserInfo.get().isChrome()) {
// Chrome supports "off" and random number does not work with
// Chrome
textBox.getElement().setAttribute("autocomplete", "off");
} else {
textBox.getElement().setAttribute("autocomplete", Math.random() + "");
}
}
}

+ 1
- 1
client/src/main/java/com/vaadin/client/communication/DefaultConnectionStateHandler.java View File

@@ -146,7 +146,7 @@ public class DefaultConnectionStateHandler implements ConnectionStateHandler {
int statusCode = response.getStatusCode();
getLogger().warning("Heartbeat request returned " + statusCode);

if (response.getStatusCode() == Response.SC_GONE) {
if (response.getStatusCode() == Response.SC_FORBIDDEN) {
// Session expired
getConnection().showSessionExpiredError(null);
stopApplication();

+ 56
- 7
client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java View File

@@ -43,10 +43,13 @@ import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.connectors.AbstractListingConnector;
import com.vaadin.client.connectors.grid.ColumnConnector.CustomColumn;
import com.vaadin.client.data.AbstractRemoteDataSource;
import com.vaadin.client.data.DataSource;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.widget.escalator.RowContainer;
import com.vaadin.client.widget.grid.CellReference;
import com.vaadin.client.widget.grid.DataAvailableEvent;
import com.vaadin.client.widget.grid.DataAvailableHandler;
import com.vaadin.client.widget.grid.EventCellReference;
import com.vaadin.client.widget.grid.events.BodyClickHandler;
import com.vaadin.client.widget.grid.events.BodyDoubleClickHandler;
@@ -93,13 +96,15 @@ public class GridConnector extends AbstractListingConnector
* The scrolling methods must trigger the scrolling only after any potential
* resizing or other similar action triggered from the server side within
* the same round trip has had a chance to happen, so there needs to be a
* delay. The delay is done with <code>scheduleFinally</code> rather than
* <code>scheduleDeferred</code> because the latter has been known to cause
* flickering in Grid.
* delay. The delay is done with <code>scheduleDeferred</code> rather than
* <code>scheduleFinally</code> because otherwise the order of the
* operations isn't guaranteed.
*
*/
private class GridConnectorClientRpc implements GridClientRpc {
private final Grid<JsonObject> grid;
private HandlerRegistration dataAvailableHandlerRegistration = null;
private boolean recalculateScheduled = false;

private GridConnectorClientRpc(Grid<JsonObject> grid) {
this.grid = grid;
@@ -107,7 +112,7 @@ public class GridConnector extends AbstractListingConnector

@Override
public void scrollToRow(int row, ScrollDestination destination) {
Scheduler.get().scheduleFinally(() -> {
Scheduler.get().scheduleDeferred(() -> {
grid.scrollToRow(row, destination);
// Add details refresh listener and handle possible detail
// for scrolled row.
@@ -121,12 +126,12 @@ public class GridConnector extends AbstractListingConnector

@Override
public void scrollToStart() {
Scheduler.get().scheduleFinally(() -> grid.scrollToStart());
Scheduler.get().scheduleDeferred(() -> grid.scrollToStart());
}

@Override
public void scrollToEnd() {
Scheduler.get().scheduleFinally(() -> {
Scheduler.get().scheduleDeferred(() -> {
grid.scrollToEnd();
addDetailsRefreshCallback(() -> {
if (rowHasDetails(grid.getDataSource().size() - 1)) {
@@ -138,7 +143,51 @@ public class GridConnector extends AbstractListingConnector

@Override
public void recalculateColumnWidths() {
grid.recalculateColumnWidths();
if (recalculateScheduled) {
return;
}

// Must be scheduled so that possible refreshAll has time to clear
// the cache.
recalculateScheduled = true;
Scheduler.get().scheduleFinally(() -> {
// If cache has been cleared, wait for data to become available.
// Don't trigger another attempt if there is already a handler
// waiting, that one will trigger the call when calculations are
// possible and clear out the registration afterwards.
if (((AbstractRemoteDataSource<JsonObject>) getDataSource())
.getCachedRange().length() == 0
&& getDataSource().size() > 0) {
if (dataAvailableHandlerRegistration == null) {
dataAvailableHandlerRegistration = grid
.addDataAvailableHandler(
new DataAvailableHandler() {

@Override
public void onDataAvailable(
DataAvailableEvent event) {
if (event.getAvailableRows()
.length() == 0
&& getDataSource()
.size() > 0) {
// Cache not populated yet,
// wait for next call.
return;
}
grid.recalculateColumnWidths();
if (dataAvailableHandlerRegistration != null) {
dataAvailableHandlerRegistration
.removeHandler();
dataAvailableHandlerRegistration = null;
}
}
});
}
} else if (dataAvailableHandlerRegistration == null) {
grid.recalculateColumnWidths();
}
recalculateScheduled = false;
});
}
}


+ 39
- 3
client/src/main/java/com/vaadin/client/ui/ShortcutActionHandler.java View File

@@ -126,15 +126,31 @@ public class ShortcutActionHandler {
target = Util.findPaintable(client, et);
}
final ComponentConnector finalTarget = target;

event.preventDefault();

/*
* The focused component might have unpublished changes, try to
* synchronize them before firing shortcut action.
*/
client.flushActiveConnector();

/*
* Legacy components don't have built-in logic for flushing, they need a
* workaround with blur and focus to trigger the value change.
*/
ComponentConnector activeConnector = getActiveConnector(client);
if (activeConnector != null) {
Class<?> clz = activeConnector.getClass();
while (clz != null) {
if (clz.getName().equals(
"com.vaadin.v7.client.ui.AbstractLegacyComponentConnector")) {
shakeTarget(et);
Scheduler.get().scheduleDeferred(() -> {
shakeTarget(et);
});
break;
}
clz = clz.getSuperclass();
}
}
Scheduler.get().scheduleDeferred(() -> {
if (finalTarget != null) {
client.updateVariable(paintableId, "actiontarget", finalTarget,
@@ -144,6 +160,26 @@ public class ShortcutActionHandler {
});
}

/**
* We try to fire value change in the component the key combination was
* typed. E.g. TextField may contain newly typed text that is expected to be
* sent to server before the shortcut action is triggered. This is done by
* removing focus and then returning it immediately back to target element.
* <p>
* This is a hack copied over from V7 in order to keep the compatibility
* classes working. Main V8 classes don't require shaking.
*/
private static void shakeTarget(final Element e) {
blur(e);
focus(e);
}

private static native ComponentConnector getActiveConnector(
ApplicationConnection ac)
/*-{
return ac.@com.vaadin.client.ApplicationConnection::getActiveConnector()();
}-*/;

private static native void blur(Element e)
/*-{
if (e.blur) {

+ 34
- 10
client/src/main/java/com/vaadin/client/ui/VAbstractCalendarPanel.java View File

@@ -16,6 +16,8 @@

package com.vaadin.client.ui;

import static com.vaadin.client.DateTimeService.asTwoDigits;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -30,7 +32,6 @@ import com.google.gwt.aria.client.Roles;
import com.google.gwt.aria.client.SelectedValue;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -56,14 +57,11 @@ import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.InlineHTML;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.DateTimeService;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.util.SharedUtil;

import static com.vaadin.client.DateTimeService.asTwoDigits;

/**
* Abstract calendar panel to show and select a date using a resolution. The
* class is parameterized by the date resolution enumeration type.
@@ -764,8 +762,18 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
return true;
}

// If dateStrResolution has more year digits than rangeEnd, we need
// to pad it in order to be lexicographically compatible
String dateStrResolution = dateStrResolution(date, minResolution);
return rangeEnd.substring(0, dateStrResolution.length())
String paddedEnd = rangeEnd.substring(0);
int yearDigits = dateStrResolution.indexOf("-");
if (yearDigits == -1) {
yearDigits = dateStrResolution.length();
}
while (paddedEnd.indexOf("-") < yearDigits) {
paddedEnd = "0" + paddedEnd;
}
return paddedEnd.substring(0, dateStrResolution.length())
.compareTo(dateStrResolution) >= 0;
}

@@ -963,7 +971,13 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
* resolution of the calendar is changed and no date has been
* selected.
*/
@SuppressWarnings("rawtypes")
public void renderCalendar(boolean updateDate) {
if (parent instanceof VAbstractPopupCalendar
&& !((VAbstractPopupCalendar) parent).popup.isShowing()) {
// a popup that isn't open cannot possibly need a focus change event
updateDate = false;
}
doRenderCalendar(updateDate);

initialRenderDone = true;
@@ -986,11 +1000,15 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
getDateField().getStylePrimaryName() + "-calendarpanel");

if (focusedDate == null) {
Date now = new Date();
Date date = getDate();
if (date == null) {
date = new Date();
}
// focusedDate must have zero hours, mins, secs, millisecs
focusedDate = new FocusedDate(now.getYear(), now.getMonth(),
now.getDate());
displayedMonth = new FocusedDate(now.getYear(), now.getMonth(), 1);
focusedDate = new FocusedDate(date.getYear(), date.getMonth(),
date.getDate());
displayedMonth = new FocusedDate(date.getYear(), date.getMonth(),
1);
}

if (updateDate && !isDay(getResolution())
@@ -2114,7 +2132,13 @@ public abstract class VAbstractCalendarPanel<R extends Enum<R>>
*/
public void setRangeEnd(String newRangeEnd) {
if (!SharedUtil.equals(rangeEnd, newRangeEnd)) {
rangeEnd = newRangeEnd;
// Dates with year 10000 or more has + prefix, which is not compatible
// with format returned by dateStrResolution method
if (newRangeEnd.startsWith("+")) {
rangeEnd = newRangeEnd.substring(1);
} else {
rangeEnd = newRangeEnd;
}
if (initialRenderDone) {
// Dynamic updates to the range needs to render the calendar to
// update the element stylenames

+ 3
- 0
client/src/main/java/com/vaadin/client/ui/VAbstractTextualDate.java View File

@@ -36,6 +36,7 @@ import com.vaadin.client.BrowserInfo;
import com.vaadin.client.Focusable;
import com.vaadin.client.LocaleNotLoadedException;
import com.vaadin.client.LocaleService;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.client.ui.aria.HandlesAriaCaption;
import com.vaadin.client.ui.aria.HandlesAriaInvalid;
@@ -90,6 +91,8 @@ public abstract class VAbstractTextualDate<R extends Enum<R>>
if (BrowserInfo.get().isIE()) {
addDomHandler(this, KeyDownEvent.getType());
}
// Stop the browser from showing its own suggestion popup.
WidgetUtil.disableBrowserAutocomplete(text);
add(text);
publishJSHelpers(getElement());
}

+ 75
- 27
client/src/main/java/com/vaadin/client/ui/VComboBox.java View File

@@ -81,6 +81,7 @@ import com.vaadin.client.ui.aria.HandlesAriaRequired;
import com.vaadin.client.ui.combobox.ComboBoxConnector;
import com.vaadin.client.ui.menubar.MenuBar;
import com.vaadin.client.ui.menubar.MenuItem;
import com.vaadin.client.ui.orderedlayout.Slot;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.util.SharedUtil;
@@ -899,19 +900,33 @@ public class VComboBox extends Composite implements Field, KeyDownHandler,
}
}

if (offsetWidth + menuMarginBorderPaddingWidth
+ left < VComboBox.this.getAbsoluteLeft()
+ VComboBox.this.getOffsetWidth()) {
// Popup doesn't reach all the way to the end of the input
// field, filtering may have changed the popup width.
left = VComboBox.this.getAbsoluteLeft();
int comboBoxLeft = VComboBox.this.getAbsoluteLeft();
int comboBoxWidth = VComboBox.this.getOffsetWidth();
if (hasParentWithUnadjustedHorizontalPositioning()) {
// ComboBox itself may be incorrectly positioned, don't try to
// adjust horizontal popup position yet. Earlier width
// calculations must be performed anyway to avoid flickering.
if (top != topPosition) {
// Variable 'left' still contains the original popupLeft,
// 'top' has been updated, thus vertical position needs
// adjusting.
setPopupPosition(left, top);
}
return;
}
if (left > comboBoxLeft
|| offsetWidth + menuMarginBorderPaddingWidth
+ left < comboBoxLeft + comboBoxWidth) {
// Popup is positioned too far right or doesn't reach all the
// way to the end of the input field, filtering may have changed
// the popup width.
left = comboBoxLeft;
}
if (offsetWidth + menuMarginBorderPaddingWidth + left > Window
.getClientWidth()) {
// Popup doesn't fit the view, needs to be opened to the left
// instead.
left = VComboBox.this.getAbsoluteLeft()
+ VComboBox.this.getOffsetWidth() - offsetWidth
left = comboBoxLeft + comboBoxWidth - offsetWidth
- (int) menuMarginBorderPaddingWidth;
}
if (left < 0) {
@@ -919,10 +934,57 @@ public class VComboBox extends Composite implements Field, KeyDownHandler,
menu.setWidth(Window.getClientWidth() + "px");
}

setPopupPosition(left, top);
// Only update the position if it has changed.
if (top != topPosition || left != getPopupLeft()) {
setPopupPosition(left, top);
}
menu.scrollSelectionIntoView();
}

/**
* Checks whether there are any {@link VHorizontalLayout}s with
* incomplete internal position calculations among this VComboBox's
* parents.
*
* @return {@code true} if unadjusted parents found, {@code false}
* otherwise
*/
private boolean hasParentWithUnadjustedHorizontalPositioning() {
/*
* If there are any VHorizontalLayouts among this VComboBox's
* parents, any spacing or expand ratio may cause incorrect
* intermediate positioning. The status of the layout's internal
* positioning can be checked from the first slot's margin-left
* style, which will be set to 0px if no spacing or expand ratio
* adjustments are needed, and to a negative pixel amount if they
* are. If the style hasn't been set at all, calculations are still
* underway. Popup position shouldn't be adjusted before such
* calculations have been finished.
*
* VVerticalLayout has the same logic but it only affects the
* vertical positioning, which is irrelevant for the calculations
* here.
*/
Widget toCheck = VComboBox.this;
while (toCheck != null && !(toCheck.getParent() instanceof VUI)) {
toCheck = toCheck.getParent();
if (toCheck instanceof VHorizontalLayout) {
VHorizontalLayout hLayout = (VHorizontalLayout) toCheck;
// because hLayout is a parent it must have at least one
// child widget
Widget slot = hLayout.getWidget(0);
if (slot instanceof Slot && slot.getElement().getStyle()
.getMarginLeft().isEmpty()) {
// margin hasn't been set, layout's internal positioning
// is still being adjusted and ComboBox's position may
// not be final
return true;
}
}
}
return false;
}

/**
* Adds in-line CSS rules to the DOM according to the
* suggestionPopupWidth field
@@ -1376,24 +1438,7 @@ public class VComboBox extends Composite implements Field, KeyDownHandler,
* @since 7.6.4
*/
public FilterSelectTextBox() {
/*-
* Stop the browser from showing its own suggestion popup.
*
* Using an invalid value instead of "off" as suggested by
* https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
*
* Leaving the non-standard Safari options autocapitalize and
* autocorrect untouched since those do not interfere in the same
* way, and they might be useful in a combo box where new items are
* allowed.
*/
if (BrowserInfo.get().isChrome()) {
// Chrome supports "off" and random number does not work with
// Chrome
getElement().setAttribute("autocomplete", "off");
} else {
getElement().setAttribute("autocomplete", Math.random() + "");
}
WidgetUtil.disableBrowserAutocomplete(this);
}

/**
@@ -1983,6 +2028,9 @@ public class VComboBox extends Composite implements Field, KeyDownHandler,

/** For internal use only. May be removed or replaced in the future. */
public void updateReadOnly() {
if (readonly) {
suggestionPopup.hide();
}
debug("VComboBox: updateReadOnly()");
tb.setReadOnly(readonly || !textInputEnabled);
}

+ 2
- 0
client/src/main/java/com/vaadin/client/ui/VContextMenu.java View File

@@ -21,6 +21,7 @@ import java.util.Locale;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.dom.client.BlurEvent;
@@ -155,6 +156,7 @@ public class VContextMenu extends VOverlay implements SubPartAware {
setHeight(Window.getClientHeight() + "px");
}
setPopupPosition(menuLeft, menuTop);
getElement().getStyle().setPosition(Style.Position.FIXED);

/*
* Move keyboard focus to menu, deferring the focus setting so the

+ 2
- 2
client/src/main/java/com/vaadin/client/ui/VRichTextArea.java View File

@@ -365,7 +365,7 @@ public class VRichTextArea extends Composite implements Field, KeyPressHandler,
BrowserInfo browser = BrowserInfo.get();
String result = getValue();
if (browser.isFirefox()) {
if ("<br>".equals(result)) {
if ("<br>".equals(result) || "<div><br></div>".equals(result)) {
result = "";
}
} else if (browser.isWebkit() || browser.isEdge()) {
@@ -373,7 +373,7 @@ public class VRichTextArea extends Composite implements Field, KeyPressHandler,
result = "";
}
} else if (browser.isIE()) {
if ("<P>&nbsp;</P>".equals(result)) {
if ("<P>&nbsp;</P>".equals(result) || "<p><br></p>".equals(result)) {
result = "";
}
} else if (browser.isOpera()) {

+ 65
- 8
client/src/main/java/com/vaadin/client/ui/VUpload.java View File

@@ -40,6 +40,7 @@ import com.vaadin.client.ConnectorMap;
import com.vaadin.client.StyleConstants;
import com.vaadin.client.ui.upload.UploadConnector;
import com.vaadin.client.ui.upload.UploadIFrameOnloadStrategy;
import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.upload.UploadServerRpc;

/**
@@ -57,8 +58,7 @@ public class VUpload extends SimplePanel {
public void onBrowserEvent(Event event) {
super.onBrowserEvent(event);
if (event.getTypeInt() == Event.ONCHANGE) {
if (isImmediateMode() && fu.getFilename() != null
&& !fu.getFilename().isEmpty()) {
if (isImmediateMode() && hasFilename()) {
submit();
}
} else if (BrowserInfo.get().isIE()
@@ -121,6 +121,15 @@ public class VUpload extends SimplePanel {

private boolean immediateMode;

/**
* Just-in-case option to override the default assumption that if no file
* has been selected, no upload attempt should happen. Not part of public
* API so can be removed without a warning -- if you have an actual need for
* this feature (which is currently only accessible through violator
* pattern), let us know.
*/
private boolean allowUploadWithoutFilename = false;

private String acceptMimeTypes;

private Hidden maxfilesize = new Hidden();
@@ -143,6 +152,19 @@ public class VUpload extends SimplePanel {
setWidget(panel);
panel.add(maxfilesize);
panel.add(fu);
fu.addChangeHandler(event -> {
if (!isImmediateMode()) {
updateEnabledForSubmitButton();
}
if (client != null) {
UploadConnector connector = ((UploadConnector) ConnectorMap
.get(client).getConnector(VUpload.this));
if (connector.hasEventListener(EventId.CHANGE)) {
connector.getRpcProxy(UploadServerRpc.class)
.change(fu.getFilename());
}
}
});
submitButton = new VButton();
submitButton.addClickHandler(event -> {
if (isImmediateMode()) {
@@ -179,6 +201,7 @@ public class VUpload extends SimplePanel {
fu.unsinkEvents(Event.ONCHANGE);
fu.unsinkEvents(Event.ONFOCUS);
}
updateEnabledForSubmitButton();
}
setStyleName(getElement(), CLASSNAME + "-immediate", immediateMode);
}
@@ -204,20 +227,20 @@ public class VUpload extends SimplePanel {

/** For internal use only. May be removed or replaced in the future. */
public void disableUpload() {
setEnabledForSubmitButton(false);
if (!submitted) {
// Cannot disable the fileupload while submitting or the file won't
// be submitted at all
fu.getElement().setPropertyBoolean("disabled", true);
}
enabled = false;
updateEnabledForSubmitButton();
}

/** For internal use only. May be removed or replaced in the future. */
public void enableUpload() {
setEnabledForSubmitButton(true);
fu.getElement().setPropertyBoolean("disabled", false);
enabled = true;
updateEnabledForSubmitButton();
if (submitted) {
/*
* An old request is still in progress (most likely cancelled),
@@ -241,9 +264,27 @@ public class VUpload extends SimplePanel {
}
}

private void setEnabledForSubmitButton(boolean enabled) {
submitButton.setEnabled(enabled);
submitButton.setStyleName(StyleConstants.DISABLED, !enabled);
/**
* Updates the enabled status for submit button. If the widget itself is
* disabled, so is the submit button. It must also follow overall enabled
* status in immediate mode, otherwise you cannot select a file at all. In
* non-immediate mode there is another button for selecting the file, so the
* submit button should be disabled until a file has been selected, unless
* upload without selection has been specifically allowed.
*/
private void updateEnabledForSubmitButton() {
if (enabled && (isImmediateMode() || hasFilename()
|| allowUploadWithoutFilename)) {
submitButton.setEnabled(true);
submitButton.setStyleName(StyleConstants.DISABLED, false);
} else {
submitButton.setEnabled(false);
submitButton.setStyleName(StyleConstants.DISABLED, true);
}
}

private boolean hasFilename() {
return fu.getFilename() != null && !fu.getFilename().isEmpty();
}

/**
@@ -264,6 +305,19 @@ public class VUpload extends SimplePanel {
if (isImmediateMode()) {
fu.sinkEvents(Event.ONCHANGE);
}
fu.addChangeHandler(event -> {
if (!isImmediateMode()) {
updateEnabledForSubmitButton();
}
if (client != null) {
UploadConnector connector = ((UploadConnector) ConnectorMap
.get(client).getConnector(VUpload.this));
if (connector.hasEventListener(EventId.CHANGE)) {
connector.getRpcProxy(UploadServerRpc.class)
.change(fu.getFilename());
}
}
});
}

/**
@@ -339,7 +393,10 @@ public class VUpload extends SimplePanel {
.info("Submit cancelled (disabled or already submitted)");
return;
}
if (fu.getFilename().isEmpty()) {
if (!hasFilename()) {
if (!allowUploadWithoutFilename) {
return;
}
getLogger().info("Submitting empty selection (no file)");
}
// flush possibly pending variable changes, so they will be handled

+ 10
- 4
client/src/main/java/com/vaadin/client/ui/VWindow.java View File

@@ -1014,14 +1014,20 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
// if clicked or key ENTER or SPACE is pressed
} else if (isClosable() && target == closeBox) {
if (type == Event.ONCLICK || (type == Event.ONKEYUP
&& isKeyEnterOrSpace(event.getKeyCode()))) {
onCloseClick();
&& (isKeyEnterOrSpace(event.getKeyCode()))
|| event.getKeyCode() == KeyCodes.KEY_ESCAPE)) {
closeWindow();
}
bubble = false;
} else if (target == maximizeRestoreBox) {
// if ESC is pressed, close the window
if (type == Event.ONKEYUP
&& event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
closeWindow();
}
// handled in connector
// if clicked or key ENTER or SPACE is pressed
if (type != Event.ONCLICK && !(type == Event.ONKEYUP
else if (type != Event.ONCLICK && !(type == Event.ONKEYUP
&& isKeyEnterOrSpace(event.getKeyCode()))) {
bubble = false;
}
@@ -1097,7 +1103,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
}

private void onCloseClick() {
private void closeWindow() {
// Send the close event to the server
client.updateVariable(id, "close", true, true);
}

+ 6
- 1
client/src/main/java/com/vaadin/client/ui/optiongroup/RadioButtonGroupConnector.java View File

@@ -86,7 +86,12 @@ public class RadioButtonGroupConnector

@OnStateChange("readOnly")
void updateWidgetReadOnly() {
getWidget().setEnabled(isEnabled() && !isReadOnly());
getWidget().setReadonly(isReadOnly());
}

@OnStateChange("enabled")
void updateWidgetEnabled() {
getWidget().setEnabled(isEnabled());
}

@OnStateChange("selectedItemKey")

+ 10
- 0
client/src/main/java/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java View File

@@ -570,11 +570,13 @@ public abstract class AbstractOrderedLayoutConnector
}

// Add all necessary listeners
boolean listenersAdded = false;
if (needsFixedHeight()) {
slot.setWidgetResizeListener(childComponentResizeListener);
if (slot.hasCaption()) {
slot.setCaptionResizeListener(slotCaptionResizeListener);
}
listenersAdded = true;
} else if ((hasChildrenWithRelativeHeight
|| hasChildrenWithRelativeWidth) && slot.hasCaption()) {
/*
@@ -586,6 +588,7 @@ public abstract class AbstractOrderedLayoutConnector
* as the relative size?
*/
slot.setCaptionResizeListener(slotCaptionResizeListener);
listenersAdded = true;
}

if (needsExpand()) {
@@ -594,6 +597,13 @@ public abstract class AbstractOrderedLayoutConnector
if (slot.hasSpacing()) {
slot.setSpacingResizeListener(spacingResizeListener);
}
listenersAdded = true;
}

if (listenersAdded) {
// removing these listeners makes widget unmeasurable and resets the
// measured height, measure again if listeners got added back
getLayoutManager().setNeedsMeasure(child);
}
}


+ 25
- 1
client/src/main/java/com/vaadin/client/ui/richtextarea/VRichTextToolbar.java View File

@@ -242,7 +242,7 @@ public class VRichTextToolbar extends Composite {
final String url = Window.prompt("Enter a link URL:",
"http://");
if (url != null) {
extended.createLink(url);
createLinkViaJSNI(extended, url);
}
} else if (sender == removeLink) {
extended.removeLink();
@@ -273,6 +273,30 @@ public class VRichTextToolbar extends Composite {
updateStatus();
}
}

private native void createLinkViaJSNI(
RichTextArea.ExtendedFormatter formatter, String url)
/*-{
var elem = formatter.@com.google.gwt.user.client.ui.impl.RichTextAreaImpl::elem;
var wnd = elem.contentWindow;
var selectedText = "";
if (wnd.getSelection) {
selectedText = wnd.getSelection().toString();
}
wnd.focus();
if (selectedText) {
// Add url as the href property of the highlighted text
wnd.document.execCommand("createLink", false, url);
} else {
// Insert url both as a new text and its href-property value
var range = wnd.document.getSelection().getRangeAt(0)
var node = wnd.document.createElement("a");
node.innerHTML = url;
node.setAttribute("href", url);
range.insertNode(node);
}
}-*/;
}

private static final RichTextArea.FontSize[] FONT_SIZES_CONSTANTS = {

+ 24
- 25
client/src/main/java/com/vaadin/client/ui/upload/UploadConnector.java View File

@@ -19,13 +19,12 @@ package com.vaadin.client.ui.upload;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
import com.vaadin.client.annotations.OnStateChange;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.VUpload;
import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.upload.UploadClientRpc;
import com.vaadin.shared.ui.upload.UploadServerRpc;
import com.vaadin.shared.ui.upload.UploadState;
import com.vaadin.ui.Upload;

@@ -37,18 +36,6 @@ public class UploadConnector extends AbstractComponentConnector
registerRpc(UploadClientRpc.class, () -> getWidget().submit());
}

@Override
protected void init() {
super.init();

getWidget().fu.addChangeHandler(event -> {
if (hasEventListener(EventId.CHANGE)) {
getRpcProxy(UploadServerRpc.class)
.change(getWidget().fu.getFilename());
}
});
}

@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
if (!isRealUpdate(uidl)) {
@@ -66,17 +53,6 @@ public class UploadConnector extends AbstractComponentConnector
final String action = client
.translateVaadinUri(uidl.getStringVariable("action"));
upload.element.setAction(action);
if (uidl.hasAttribute("buttoncaption")) {
upload.submitButton
.setText(uidl.getStringAttribute("buttoncaption"));
if (uidl.hasAttribute("buttonstylename")) {
upload.submitButton.setStyleName(
uidl.getStringAttribute("buttonstylename"));
}
upload.submitButton.setVisible(true);
} else {
upload.submitButton.setVisible(false);
}
upload.fu.setName(upload.paintableId + "_file");

if (!isEnabled()) {
@@ -95,6 +71,29 @@ public class UploadConnector extends AbstractComponentConnector
getWidget().disableTitle(hasTooltip());
}

/**
* Updates the caption, style name, display mode, and visibility of the
* submit button.
* <p>
* For internal use only. May be removed or replaced in the future.
*/
@OnStateChange({ "buttonCaption", "buttonStyleName",
"buttonCaptionAsHtml" })
private void updateSubmitButton() {
VUpload upload = getWidget();
if (getState().buttonCaption != null) {
if (getState().buttonCaptionAsHtml) {
upload.submitButton.setHtml(getState().buttonCaption);
} else {
upload.submitButton.setText(getState().buttonCaption);
}
upload.submitButton.setStyleName(getState().buttonStyleName);
upload.submitButton.setVisible(true);
} else {
upload.submitButton.setVisible(false);
}
}

@Override
public VUpload getWidget() {
return (VUpload) super.getWidget();

+ 8
- 2
client/src/main/java/com/vaadin/client/ui/window/WindowConnector.java View File

@@ -433,11 +433,17 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
// This had to be here because we might not know the content size before
// everything is painted into the window

// centered is this is unset on move/resize
// centered if this is unset on move/resize
window.centered = state.centered;
// Ensure centering before setting visible (#16486)
if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) {
Scheduler.get().scheduleFinally(() -> getWidget().center());
Scheduler.get().scheduleFinally(() -> {
// the window may have got removed again before this is
// triggered, and centering would re-display it
if (getWidget().isShowing()) {
getWidget().center();
}
});
}
window.setVisible(true);
}

+ 70
- 11
client/src/main/java/com/vaadin/client/widgets/Escalator.java View File

@@ -37,6 +37,7 @@ import com.google.gwt.animation.client.AnimationScheduler;
import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback;
import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
@@ -2240,6 +2241,10 @@ public class Escalator extends Widget

TableCellElement cellClone = TableCellElement
.as((Element) cell.cloneNode(withContent));
if (!withContent || columnConfiguration
.getColumnWidth(cell.getCellIndex()) < 0) {
clearRelativeWidthContents(cellClone);
}
cellClone.getStyle().clearHeight();
cellClone.getStyle().clearWidth();

@@ -2259,6 +2264,41 @@ public class Escalator extends Widget
return requiredWidth;
}

/**
* Contents of an element that is configured to have relative width
* shouldn't be taken into consideration when measuring minimum widths.
* Thus any such contents within the element hierarchy need to be
* cleared out for accurate results. The element itself should remain,
* however, in case it has styles that affect the end results.
*
* @param elem
* an element that might have unnecessary content that
* interferes with minimum width calculations
*/
private void clearRelativeWidthContents(Element elem) {
try {
String width = elem.getStyle().getWidth();
if (width != null && width.endsWith("%")) {
if (elem.hasChildNodes()) {
elem.removeAllChildren();
// add a fake child so that :empty behavior doesn't
// change
elem.setInnerHTML("<a/>");
} else {
elem.setInnerHTML(null);
}
}
} catch (JavaScriptException e) {
// no width set, move on
}
for (int i = 0; i < elem.getChildCount(); ++i) {
Node node = elem.getChild(i);
if (node instanceof Element) {
clearRelativeWidthContents((Element) node);
}
}
}

/**
* Gets the minimum width needed to display the cell properly.
*
@@ -4578,7 +4618,7 @@ public class Escalator extends Widget
// for a gap if a details row is later closed (e.g. by user)
final int addToBottom = Math.min(rowDiff,
getRowCount() - logicalTargetIndex);
final int addToTop = rowDiff - addToBottom;
final int addToTop = Math.max(rowDiff - addToBottom, 0);

if (addToTop > 0) {
fillAndPopulateEscalatorRowsIfNeeded(0,
@@ -4587,8 +4627,30 @@ public class Escalator extends Widget
updateTopRowLogicalIndex(-addToTop);
}
if (addToBottom > 0) {
fillAndPopulateEscalatorRowsIfNeeded(visualTargetIndex,
logicalTargetIndex, addToBottom);
// take into account that rows may have got added to top as
// well, affects visual but not logical indexing
fillAndPopulateEscalatorRowsIfNeeded(
visualTargetIndex + addToTop, logicalTargetIndex,
addToBottom);

// adding new rows due to resizing may have created a gap in
// the middle, check whether the existing rows need moving
double rowTop = getRowTop(oldTopRowLogicalIndex);
if (rowTop > getRowTop(visualRowOrder.get(addToTop))) {
for (int i = addToTop; i < visualTargetIndex; i++) {

final TableRowElement tr = visualRowOrder.get(i);

setRowPosition(tr, 0, rowTop);
rowTop += getDefaultRowHeight();
SpacerContainer.SpacerImpl spacer = spacerContainer
.getSpacer(oldTopRowLogicalIndex + i);
if (spacer != null) {
spacer.setPosition(0, rowTop);
rowTop += spacer.getHeight();
}
}
}
}
} else if (rowDiff < 0) {
// rows need to be removed
@@ -7713,14 +7775,11 @@ public class Escalator extends Widget
public void scrollToRowAndSpacer(final int rowIndex,
final ScrollDestination destination, final int padding)
throws IllegalArgumentException {
// wait for the layout phase to finish
Scheduler.get().scheduleFinally(() -> {
if (rowIndex != -1) {
verifyValidRowIndex(rowIndex);
}
body.scrollToRowSpacerOrBoth(rowIndex, destination, padding,
ScrollType.ROW_AND_SPACER);
});
if (rowIndex != -1) {
verifyValidRowIndex(rowIndex);
}
body.scrollToRowSpacerOrBoth(rowIndex, destination, padding,
ScrollType.ROW_AND_SPACER);
}

private static void validateScrollDestination(

+ 180
- 84
client/src/main/java/com/vaadin/client/widgets/Grid.java View File

@@ -1694,9 +1694,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
}
state = State.ACTIVATING;

grid.scrollToRow(rowIndex,
isBuffered() ? ScrollDestination.MIDDLE
: ScrollDestination.ANY,
grid.scrollToRow(rowIndex, ScrollDestination.ANY,
() -> show(rowIndex, columnIndexDOM));
}

@@ -3395,7 +3393,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
Scheduler.get().scheduleDeferred(this);
}
} else if (currentDataAvailable.isEmpty()
&& dataSource.isWaitingForData()) {
&& (dataSource.isWaitingForData()
|| escalator.getBody().getRowCount() > 0)) {
Scheduler.get().scheduleDeferred(this);
} else {
calculate();
@@ -3408,20 +3407,16 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,

/**
* Calculates and applies column widths, taking into account fixed
* widths and column expand rules
* widths and column expand rules.
*
* @param immediately
* <code>true</code> if the widths should be executed
* immediately (ignoring lazy loading completely), or
* <code>false</code> if the command should be run after a
* while (duplicate non-immediately invocations are ignored).
* @see Column#setWidth(double)
* @see Column#setExpandRatio(int)
* @see Column#setMinimumWidth(double)
* @see Column#setMaximumWidth(double)
*/
public void schedule() {
if (!isScheduled && isAttached()) {
if (!isScheduled && isAttached() && !(currentDataAvailable.isEmpty()
&& escalator.getBody().getRowCount() > 0)) {
isScheduled = true;
Scheduler.get().scheduleFinally(calculateCommand);
}
@@ -3436,8 +3431,9 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,

// Make SelectAllCheckbox visible
getSelectionColumn().ifPresent(col -> {
if (getDefaultHeaderRow() == null)
if (getDefaultHeaderRow() == null) {
return;
}
HeaderCell headerCell = getDefaultHeaderRow().getCell(col);
if (headerCell.getType().equals(GridStaticCellType.WIDGET)) {
// SelectAllCheckbox is present already
@@ -3903,8 +3899,6 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
setParent(detailsWidget, null);
spacerElement.removeAllChildren();
if (getHeightMode() == HeightMode.UNDEFINED) {
// update spacer height
escalator.getBody().setSpacer(spacer.getRow(), 0);
setHeightByRows(getEscalator().getBody().getRowCount());
}
}
@@ -4108,15 +4102,25 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,

private void setHeightToHeaderCellHeight() {
RowContainer header = grid.escalator.getHeader();
if (header.getRowCount() == 0
if (!WidgetUtil.isDisplayed(header.getElement())
|| header.getRowCount() == 0
|| !header.getRowElement(0).hasChildNodes()) {
getLogger().info(
"No header cell available when calculating sidebar button height");
openCloseButton.setHeight(header.getDefaultRowHeight() + "px");
// If the Grid is hidden with styles when this is called the
// border height will be off, but it's usually only a matter of
// a pixel or so. Removing a style name cannot trigger a full
// refresh of the layout, it's developer's responsibility to do
// that where needed.
double height = header.getDefaultRowHeight()
- WidgetUtil.measureVerticalBorder(getElement()) / 2;
openCloseButton.setHeight(height + "px");

return;
}

// Use actual height instead of expected height in case the height
// is modified by styles.
Element firstHeaderCell = header.getRowElement(0)
.getFirstChildElement();
double height = WidgetUtil
@@ -4135,8 +4139,6 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
close();
grid.getElement().appendChild(getElement());
Grid.setParent(this, grid);
// border calculation won't work until attached
setHeightToHeaderCellHeight();
}
}

@@ -4147,10 +4149,9 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
@Override
protected void onAttach() {
super.onAttach();
// make sure the button will get correct height if the button should
// be visible when the grid is rendered the first time.
Scheduler.get()
.scheduleDeferred(() -> setHeightToHeaderCellHeight());
// Make sure the button will get correct height whenever the Sidebar
// is added to the Grid.
setHeightToHeaderCellHeight();
}

@Override
@@ -4183,6 +4184,12 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
*/
private boolean hidingColumn;

/**
* When several columns are set hidable, don't reset the Sidebar for
* every column separately.
*/
private boolean toggleUpdateTriggered;

private void updateColumnHidable(final Column<?, T> column) {
if (column.isHidable()) {
MenuItem toggle = columnToHidingToggleMap.get(column);
@@ -4228,16 +4235,26 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
}

private void updateTogglesOrder() {
if (!hidingColumn) {
int lastIndex = 0;
for (Column<?, T> column : getColumns()) {
if (column.isHidable()) {
final MenuItem menuItem = columnToHidingToggleMap
.get(column);
sidebar.menuBar.removeItem(menuItem);
sidebar.menuBar.insertItem(menuItem, lastIndex++);
if (!hidingColumn && !toggleUpdateTriggered) {
// This method is called whenever a column is set hidable. If
// there are multiple hidable columns, it will get called
// separately for all of them. There is no need to update the
// order more than once and no other layouting is dependent on
// the Sidebar layouting getting finished first, so wait until
// all calls have arrived before proceeding further.
toggleUpdateTriggered = true;
Scheduler.get().scheduleFinally(() -> {
int lastIndex = 0;
for (Column<?, T> column : getColumns()) {
if (column.isHidable()) {
final MenuItem menuItem = columnToHidingToggleMap
.get(column);
sidebar.menuBar.removeItem(menuItem);
sidebar.menuBar.insertItem(menuItem, lastIndex++);
}
}
}
toggleUpdateTriggered = false;
});
}
}

@@ -4370,6 +4387,9 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,

private boolean refreshBodyRequested = false;

private boolean resizeRequested = false;
private boolean resizeRefreshScheduled = false;

private DragAndDropHandler.DragAndDropCallback headerCellDndCallback = new DragAndDropCallback() {

private final AutoScrollerCallback autoScrollerCallback = new AutoScrollerCallback() {
@@ -5302,7 +5322,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
} else {
this.hidden = hidden;

final int columnIndex = grid.getVisibleColumns()
int columnIndex = grid.getVisibleColumns()
.indexOf(this);
grid.escalator.getColumnConfiguration()
.insertColumns(columnIndex, 1);
@@ -5311,10 +5331,16 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
// escalator doesn't handle situation where the added column
// would be the last frozen column
int gridFrozenColumns = grid.getFrozenColumnCount();
// Correct column index for multiselect mode
if (grid.getSelectionColumn().isPresent()) {
gridFrozenColumns++;
}
int escalatorFrozenColumns = grid.escalator
.getColumnConfiguration().getFrozenColumnCount();
if (gridFrozenColumns > escalatorFrozenColumns
&& escalatorFrozenColumns == columnIndex) {
&& escalatorFrozenColumns == columnIndex
&& grid.getColumns()
.indexOf(this) < gridFrozenColumns) {
grid.escalator.getColumnConfiguration()
.setFrozenColumnCount(++escalatorFrozenColumns);
}
@@ -7224,6 +7250,8 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
this.dataSource = dataSource;
changeHandler = dataSource
.addDataChangeHandler(new DataChangeHandler() {
private boolean recalculateColumnWidthsNeeded = false;

@Override
public void dataUpdated(int firstIndex, int numberOfItems) {
escalator.getBody().refreshRows(firstIndex,
@@ -7256,11 +7284,23 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
int numberOfItems) {
currentDataAvailable = Range.withLength(firstIndex,
numberOfItems);
if (recalculateColumnWidthsNeeded) {
// Ensure that cache has actually been populated or
// all rows removed, otherwise wait for next call.
if (numberOfItems > 0
|| getDataSource().size() == 0) {
recalculateColumnWidths();
recalculateColumnWidthsNeeded = false;
}
}
fireEvent(new DataAvailableEvent(currentDataAvailable));
}

@Override
public void resetDataAndSize(int newSize) {
// It might take a while for new data to arrive,
// clear the record of cached rows.
currentDataAvailable = Range.emptyRange();
RowContainer body = escalator.getBody();
int oldSize = body.getRowCount();

@@ -7276,8 +7316,9 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
// Need to recalculate column widths when the
// first row is added to a non-header grid,
// otherwise the checkbox will be aligned in a
// wrong place.
recalculateColumnWidths();
// wrong place. Wait until the cache has been
// populated before making the call.
recalculateColumnWidthsNeeded = true;
}
body.insertRows(oldSize, newSize - oldSize);
cellFocusHandler.rowsAddedToBody(Range
@@ -7296,8 +7337,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
visibleRowRange.length());
} else {
// We won't expect any data more data updates, so
// just make
// the bookkeeping happy
// just make the bookkeeping happy.
dataAvailable(0, 0);
}

@@ -7358,8 +7398,17 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
}

private void updateFrozenColumns() {
escalator.getColumnConfiguration()
.setFrozenColumnCount(getVisibleFrozenColumnCount());
int visibleFrozenColumnCount = getVisibleFrozenColumnCount();
ColumnConfiguration columnConfiguration = escalator
.getColumnConfiguration();
if (columnConfiguration.getColumnCount() < visibleFrozenColumnCount) {
// new columns may not have got added yet, delay and check the
// correct count again
Scheduler.get().scheduleFinally(() -> columnConfiguration
.setFrozenColumnCount(getVisibleFrozenColumnCount()));
} else {
columnConfiguration.setFrozenColumnCount(visibleFrozenColumnCount);
}
}

private int getVisibleFrozenColumnCount() {
@@ -7367,7 +7416,15 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,

// for the escalator the hidden columns are not in the frozen column
// count, but for grid they are. thus need to convert the index
for (int i = 0; i < frozenColumnCount; i++) {
int limit = getFrozenColumnCount();
if (getSelectionColumn().isPresent()) {
// If the grid is in MultiSelect mode, getColumn(0) in the following
// for loop returns the selection column. Accordingly, verifying
// which frozen columns are visible if the selection column is
// present should take this fact into account.
limit++;
}
for (int i = 0; i < limit; i++) {
if (i >= getColumnCount() || getColumn(i).isHidden()) {
numberOfColumns--;
}
@@ -7459,15 +7516,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
*/
public void scrollToRow(int rowIndex, ScrollDestination destination,
Runnable callback) {
waitUntilVisible(rowIndex, destination, () -> {
Reference<HandlerRegistration> registration = new Reference<>();
registration.set(addDataAvailableHandler(event -> {
if (event.getAvailableRows().contains(rowIndex)) {
registration.get().removeHandler();
callback.run();
}
}));
});
waitUntilVisible(rowIndex, destination, callback);
}

/**
@@ -7533,29 +7582,61 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
*/
private void waitUntilVisible(int rowIndex, ScrollDestination destination,
Runnable whenVisible) {
final Escalator escalator = getEscalator();
if (escalator.getVisibleRowRange().contains(rowIndex)) {
TableRowElement rowElement = escalator.getBody()
.getRowElement(rowIndex);
long bottomBorder = Math.round(WidgetUtil.getBorderBottomThickness(
rowElement.getFirstChildElement()) + 0.5d);
if (rowElement.getAbsoluteTop() + bottomBorder >= escalator
.getHeader().getElement().getAbsoluteBottom()
&& rowElement.getAbsoluteBottom() <= escalator.getFooter()
.getElement().getAbsoluteTop() + bottomBorder) {
whenVisible.run();
return;
}
boolean waitForCache = false;
if (getDataSource().getRow(rowIndex) == null) {
// not yet in cache, wait for this to change
waitForCache = true;

Reference<Registration> registration = new Reference<>();
registration.set(getDataSource()
.addDataChangeHandler(new DataChangeHandler() {
@Override
public void resetDataAndSize(int estimatedNewDataSize) {
// data set changed, cancel the operation
registration.get().remove();
}

@Override
public void dataUpdated(int firstRowIndex,
int numberOfRows) {
// NOP
}

@Override
public void dataRemoved(int firstRowIndex,
int numberOfRows) {
// data set changed, cancel the operation
registration.get().remove();
}

@Override
public void dataAvailable(int firstRowIndex,
int numberOfRows) {
// if new available range contains the row,
// try again
if (Range.withLength(firstRowIndex, numberOfRows)
.contains(rowIndex)) {
registration.get().remove();
waitUntilVisible(rowIndex, destination,
whenVisible);
}
}

@Override
public void dataAdded(int firstRowIndex,
int numberOfRows) {
// data set changed, cancel the operation
registration.get().remove();
}
}));
}

Reference<HandlerRegistration> registration = new Reference<>();
registration.set(addScrollHandler(event -> {
if (escalator.getVisibleRowRange().contains(rowIndex)) {
registration.get().removeHandler();
whenVisible.run();
}
}));
scrollToRow(rowIndex, destination);

if (!waitForCache) {
// all necessary adjustments done, time to perform
whenVisible.run();
}
}

/**
@@ -9244,23 +9325,38 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
/*
* Delay calculation to be deferred so Escalator can do it's magic.
*/
Scheduler.get().scheduleFinally(() -> {
if (escalator
.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) {
recalculateColumnWidths();
}
resizeRequested = true;
if (!resizeRefreshScheduled) {
resizeRefreshScheduled = true;
Scheduler.get().scheduleFixedDelay(() -> {
if (!resizeRequested) {
doRefreshOnResize();
resizeRefreshScheduled = false;
return false;
} else {
resizeRequested = false;
return true;
}
}, 50);
}
}

// Vertical resizing could make editor positioning invalid so it
// needs to be recalculated on resize
if (isEditorActive()) {
editor.updateVerticalScrollPosition();
}
private void doRefreshOnResize() {
if (escalator
.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) {
recalculateColumnWidths();
}

// if there is a resize, we need to refresh the body to avoid an
// off-by-one error which occurs when the user scrolls all the
// way to the bottom.
refreshBody();
});
// Vertical resizing could make editor positioning invalid so it
// needs to be recalculated on resize
if (isEditorActive()) {
editor.updateVerticalScrollPosition();
}

// if there is a resize, we need to refresh the body to avoid an
// off-by-one error which occurs when the user scrolls all the
// way to the bottom.
refreshBody();
}

private double getEscalatorInnerHeight() {

+ 1
- 1
compatibility-client-compiled/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-compatibility-client-compiled</artifactId>
<name>vaadin-compatibility-client-compiled</name>

+ 1
- 1
compatibility-client/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-compatibility-client</artifactId>
<name>vaadin-compatibility-client</name>

+ 2
- 17
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VFilterSelect.java View File

@@ -1409,23 +1409,8 @@ public class VFilterSelect extends Composite
* @since 7.6.4
*/
public FilterSelectTextBox() {
/*-
* Stop the browser from showing its own suggestion popup.
*
* Using an invalid value instead of "off" as suggested by
* https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
*
* Leaving the non-standard Safari options autocapitalize and
* autocorrect untouched since those do not interfere in the same
* way, and they might be useful in a combo box where new items are
* allowed.
*/
if (BrowserInfo.get().isChrome()) {
// Chrome supports "off" and random number does not work with Chrome
getElement().setAttribute("autocomplete", "off");
} else {
getElement().setAttribute("autocomplete", Math.random() + "");
}
// Stop the browser from showing its own suggestion popup.
WidgetUtil.disableBrowserAutocomplete(this);
}

/**

+ 14
- 5
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VScrollTable.java View File

@@ -349,6 +349,8 @@ public class VScrollTable extends FlowPanel

private SelectMode selectMode = SelectMode.NONE;

private boolean multiSelectTouchDetectionEnabled = true;

public final HashSet<String> selectedRowKeys = new HashSet<String>();

/*
@@ -1502,6 +1504,10 @@ public class VScrollTable extends FlowPanel
} else {
selectMode = SelectMode.NONE;
}
if (uidl.hasAttribute("touchdetection")) {
multiSelectTouchDetectionEnabled = uidl
.getBooleanAttribute("touchdetection");
}
}
}

@@ -1951,9 +1957,10 @@ public class VScrollTable extends FlowPanel
}

private void setMultiSelectMode(int multiselectmode) {
if (BrowserInfo.get().isTouchDevice()) {
if (BrowserInfo.get().isTouchDevice()
&& multiSelectTouchDetectionEnabled) {
// Always use the simple mode for touch devices that do not have
// shift/ctrl keys
// shift/ctrl keys (unless this feature is explicitly disabled)
this.multiselectmode = MULTISELECT_MODE_SIMPLE;
} else {
this.multiselectmode = multiselectmode;
@@ -3931,10 +3938,12 @@ public class VScrollTable extends FlowPanel
public void onBrowserEvent(Event event) {
if (enabled) {
if (event.getEventTarget().cast() == columnSelector) {
final int left = DOM.getAbsoluteLeft(columnSelector);
final int top = DOM.getAbsoluteTop(columnSelector)
WidgetUtil.TextRectangle clientRect = WidgetUtil
.getBoundingClientRect(columnSelector);
final int left = (int) clientRect.getLeft();
final int top = (int) (clientRect.getTop()
+ DOM.getElementPropertyInt(columnSelector,
"offsetHeight");
"offsetHeight"));
client.getContextMenu().showAt(this, left, top);
}
}

+ 3
- 0
compatibility-client/src/main/java/com/vaadin/v7/client/ui/VTextualDate.java View File

@@ -35,6 +35,7 @@ import com.vaadin.client.BrowserInfo;
import com.vaadin.client.Focusable;
import com.vaadin.client.LocaleNotLoadedException;
import com.vaadin.client.LocaleService;
import com.vaadin.client.WidgetUtil;
import com.vaadin.client.ui.SubPartAware;
import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.client.ui.aria.HandlesAriaCaption;
@@ -115,6 +116,8 @@ public class VTextualDate extends VDateField
if (BrowserInfo.get().isIE()) {
addDomHandler(this, KeyDownEvent.getType());
}
// Stop the browser from showing its own suggestion popup.
WidgetUtil.disableBrowserAutocomplete(text);
add(text);
}


+ 1
- 0
compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Escalator.java View File

@@ -3720,6 +3720,7 @@ public class Escalator extends Widget
visualRowOrder.getLast()) + 1;
moveAndUpdateEscalatorRows(Range.withOnly(0),
visualRowOrder.size(), newLogicalIndex);
updateTopRowLogicalIndex(1);
}
}
}

+ 46
- 24
compatibility-client/src/main/java/com/vaadin/v7/client/widgets/Grid.java View File

@@ -2145,13 +2145,19 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,

// sometimes focus handling twists the editor row out of alignment
// with the grid itself and the position needs to be compensated for
TableRowElement rowElement = grid.getEscalator().getBody()
.getRowElement(grid.getEditor().getRow());
int rowLeft = rowElement.getAbsoluteLeft();
int editorLeft = cellWrapper.getAbsoluteLeft();
if (editorLeft != rowLeft + frozenWidth) {
cellWrapper.getStyle().setLeft(newLeft + rowLeft - editorLeft,
Unit.PX);
try {
TableRowElement rowElement = grid.getEscalator().getBody()
.getRowElement(grid.getEditor().getRow());
int rowLeft = rowElement.getAbsoluteLeft();
int editorLeft = cellWrapper.getAbsoluteLeft();
if (editorLeft != rowLeft + frozenWidth) {
cellWrapper.getStyle().setLeft(newLeft + rowLeft - editorLeft,
Unit.PX);
}
} catch (IllegalStateException e) {
// IllegalStateException may occur if user has scrolled Grid so
// that Escalator has updated, and row under Editor is no longer
// there
}
}

@@ -2934,6 +2940,7 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
private CheckBox selectAllCheckBox;
private boolean userSelectionAllowed = true;
private boolean enabled = true;
private HandlerRegistration headerClickHandler;

SelectionColumn(final Renderer<Boolean> selectColumnRenderer) {
super(selectColumnRenderer);
@@ -2987,24 +2994,28 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
});
selectAllCheckBox.setValue(selected);

addHeaderClickHandler(new HeaderClickHandler() {
@Override
public void onClick(GridClickEvent event) {
if (!userSelectionAllowed) {
return;
}

CellReference<?> targetCell = event.getTargetCell();
int defaultRowIndex = getHeader().getRows()
.indexOf(getDefaultHeaderRow());
headerClickHandler = addHeaderClickHandler(
new HeaderClickHandler() {
@Override
public void onClick(GridClickEvent event) {
if (!userSelectionAllowed) {
return;
}

if (targetCell.getColumnIndex() == 0 && targetCell
.getRowIndex() == defaultRowIndex) {
selectAllCheckBox.setValue(
!selectAllCheckBox.getValue(), true);
}
}
});
CellReference<?> targetCell = event
.getTargetCell();
int defaultRowIndex = getHeader().getRows()
.indexOf(getDefaultHeaderRow());

if (targetCell.getColumnIndex() == 0
&& targetCell
.getRowIndex() == defaultRowIndex) {
selectAllCheckBox.setValue(
!selectAllCheckBox.getValue(),
true);
}
}
});

// Select all with space when "select all" cell is active
addHeaderKeyUpHandler(new HeaderKeyUpHandler() {
@@ -3143,6 +3154,13 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
getEscalator().getBody().refreshRows(0,
getEscalator().getBody().getRowCount());
}

private void cleanup() {
if (headerClickHandler != null) {
headerClickHandler.removeHandler();
headerClickHandler = null;
}
}
}

/**
@@ -7921,6 +7939,10 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>,
return;
}

if (this.selectionColumn != null) {
selectionColumn.cleanup();
}

if (this.selectColumnRenderer != null) {
if (this.selectColumnRenderer instanceof ComplexRenderer) {
// End of Life for the old selection column renderer.

+ 1
- 1
compatibility-server-gae/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-compatibility-server-gae</artifactId>
<name>vaadin-compatibility-server-gae</name>

+ 1
- 1
compatibility-server/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-compatibility-server</artifactId>
<name>vaadin-compatibility-server</name>

+ 3
- 1
compatibility-server/src/main/java/com/vaadin/v7/ui/ComboBox.java View File

@@ -620,7 +620,9 @@ public class ComboBox extends AbstractSelect
// page length usable for non-null items
int effectivePageLength = pageLength
- (needNullSelectOption && (currentPage == 0) ? 1 : 0);
return Math.min(size - 1, first + effectivePageLength - 1);
// zero pageLength implies infinite page size
return pageLength == 0 ? size - 1
: Math.min(size - 1, first + effectivePageLength - 1);
}

/**

+ 2
- 0
compatibility-server/src/main/java/com/vaadin/v7/ui/Grid.java View File

@@ -38,6 +38,7 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
@@ -3473,6 +3474,7 @@ public class Grid extends AbstractComponent
if (caption == null) {
caption = ""; // Render null as empty
}
caption = Jsoup.parse(caption).text();
state.headerCaption = caption;

HeaderRow row = grid.getHeader().getDefaultRow();

+ 35
- 1
compatibility-server/src/main/java/com/vaadin/v7/ui/Table.java View File

@@ -588,6 +588,8 @@ public class Table extends AbstractSelect implements Action.Container,

private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;

private boolean multiSelectTouchDetectionEnabled = true;

private boolean rowCacheInvalidated;

private RowGenerator rowGenerator = null;
@@ -3775,6 +3777,10 @@ public class Table extends AbstractSelect implements Action.Container,
if (isSelectable()) {
target.addAttribute("selectmode",
(isMultiSelect() ? "multi" : "single"));
if (isMultiSelect()) {
target.addAttribute("touchdetection",
isMultiSelectTouchDetectionEnabled());
}
} else {
target.addAttribute("selectmode", "none");
}
@@ -5188,7 +5194,10 @@ public class Table extends AbstractSelect implements Action.Container,
* <p>
* Note, that on some clients the mode may not be respected. E.g. on touch
* based devices CTRL/SHIFT base selection method is invalid, so touch based
* browsers always use the {@link MultiSelectMode#SIMPLE}.
* browsers always use the {@link MultiSelectMode#SIMPLE} unless touch multi
* select is explicitly disabled.
*
* @see #setMultiSelectTouchDetectionEnabled(boolean)
*
* @param mode
* The select mode of the table
@@ -5207,6 +5216,31 @@ public class Table extends AbstractSelect implements Action.Container,
return multiSelectMode;
}

/**
* Default behavior on touch-reporting devices is to switch from CTRL/SHIFT
* based multi-selection to simple mode, but you can use this method to
* explicitly disable the touch device detection. Thus you can keep using
* keyboard-based multi selection on hybrid devices that have both a touch
* screen and a keyboard.
*
* @param multiSelectTouchDetectionEnabled
* Whether to enable or disable touch screen detection
*/
public void setMultiSelectTouchDetectionEnabled(
boolean multiSelectTouchDetectionEnabled) {
this.multiSelectTouchDetectionEnabled = multiSelectTouchDetectionEnabled;
markAsDirty();
}

/**
* Returns if touch screen detection is used to toggle multi select mode.
*
* @return If touch screen detection for multi select is enabled
*/
public boolean isMultiSelectTouchDetectionEnabled() {
return multiSelectTouchDetectionEnabled;
}

/**
* Lazy loading accept criterion for Table. Accepted target rows are loaded
* from server once per drag and drop operation. Developer must override one

+ 1
- 1
compatibility-shared/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-compatibility-shared</artifactId>
<name>vaadin-compatibility-shared</name>

+ 1
- 1
compatibility-themes/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-compatibility-themes</artifactId>
<name>vaadin-compatibility-themes</name>

+ 1
- 0
compatibility-themes/src/main/themes/VAADIN/themes/base/table/table.scss View File

@@ -137,6 +137,7 @@
}
.#{$primaryStyleName}-body {
border: 1px solid #aaa;
overflow-anchor: none; /* In Chrome 56+ */
}
.#{$primaryStyleName}-row-spacer {
height: 10px;

+ 1
- 0
documentation/articles/FindingTheCurrentUIAndPageAndVaadinSession.asciidoc View File

@@ -6,6 +6,7 @@ layout: page

[[finding-the-current-ui-and-page-and-vaadin-session]]
= Finding the current UI and page and vaadin session

There are many cases where you need a reference to the active `UI`, `Page`
or `VaadinServiceSession`, for instance for showing notifications in a
click listener. It is possible to get a reference to the component from

+ 9
- 2
documentation/articles/LettingTheUserDownloadAFile.asciidoc View File

@@ -104,8 +104,15 @@ public class OnDemandFileDownloader extends FileDownloader {
return super.handleConnectorRequest(request, response, path);
}

private StreamResource getResource () {
return (StreamResource) this.getResource("dl");
private StreamResource getResource() {
StreamResource result = null;
this.getSession().lock();
try {
result = (StreamResource) this.getResource("dl");
} finally {
this.getSession().unlock();
}
return result;
}
}
....

+ 7
- 0
documentation/components/components-grid.asciidoc View File

@@ -721,6 +721,13 @@ yearsCell.setHtml("<b>Years</b>");
You can set a component in a header or footer cell with
[methodname]#setComponent()#. Often, this feature is used to allow filtering.

[NOTE]
====
Note, when you use [methodname]#setComponent(TextField)#, the [classname]#TextField# will be rendered in compact mode
without caption and icon. If you need to override this behavior, you need to wrap the [classname]#TextField# e.g.
into [classname]#HorizontalLayout#.
====

////
// commented out until filtering is sorted for 8
[[components.grid.filtering]]

+ 13
- 0
documentation/datamodel/datamodel-forms.asciidoc View File

@@ -138,6 +138,7 @@ binder.forField(nameField)

binder.forField(titleField)
// Shorthand for requiring the field to be non-empty
// This is conditional on Binding::setAsRequiredEnabled(boolean)
.asRequired("Every employee must have a title")
.bind(Person::getTitle, Person::setTitle);
----
@@ -382,6 +383,11 @@ if (saved) {
----
--

[NOTE]
Note, if you need to write the values passing the validation regardless of having one or more failing validators, you can
use binder.writeBeanAsDraft(person).


Binder keeps track of which bindings have been updated by the user and which bindings are in an invalid state.
It also fires an event when this status changes.
We can use that event to make the save and reset buttons of our forms become enabled or disabled depending on the current status of the form.
@@ -612,3 +618,10 @@ binder.forField(yearOfBirth)

binder.bindInstanceFields(this);
----

[NOTE]
If you need to bind nested proprerties with bindInstanceFields method, you need to instantiate the Binder using:
[source,java]
----
Binder<Person> binder = new Binder<>(Person.class,true);
----

+ 1
- 1
liferay-integration/pom.xml View File

@@ -9,7 +9,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>

<properties>

+ 1
- 1
liferay/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-liferay</artifactId>
<name>vaadin-liferay</name>

+ 1
- 1
osgi-integration/pom.xml View File

@@ -9,7 +9,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>

<properties>

+ 16
- 2
pom.xml View File

@@ -10,7 +10,7 @@
<artifactId>vaadin-root</artifactId>
<name>vaadin-root</name>
<packaging>pom</packaging>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>

<prerequisites>
<maven>3.1.0</maven>
@@ -61,6 +61,13 @@
</properties>

<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>vaadin-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
@@ -74,6 +81,13 @@
</pluginRepositories>

<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>vaadin-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
@@ -215,7 +229,7 @@
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
<version>4.3.2.Final</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>

+ 1
- 1
push/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-push</artifactId>
<name>vaadin-push</name>

+ 1
- 1
server/bnd.bnd View File

@@ -4,7 +4,7 @@ Bundle-Version: ${osgi.bundle.version}
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-License: http://www.apache.org/licenses/LICENSE-2.0
Import-Package: com.vaadin.sass.*;resolution:=optional,\
com.liferay.portal.kernel.util;resolution:=optional;version='[7.0.0,10.0.0)',\
com.liferay.portal.kernel.util;resolution:=optional;version='[7.0.0,11.0.0)',\
javax.portlet*;resolution:=optional,\
javax.validation*;resolution:=optional;version='${javax.validation.version}',\
org.atmosphere*;resolution:=optional;version='${atmosphere.runtime.version}',\

+ 1
- 1
server/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-server</artifactId>
<name>vaadin-server</name>

+ 5
- 2
server/src/main/java/com/vaadin/data/BeanValidationBinder.java View File

@@ -49,7 +49,7 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
* the bean type to use, not <code>null</code>
*/
public BeanValidationBinder(Class<BEAN> beanType) {
this(beanType,false);
this(beanType, false);
}

/**
@@ -65,8 +65,11 @@ public class BeanValidationBinder<BEAN> extends Binder<BEAN> {
* the bean type to use, not {@code null}
* @param scanNestedDefinitions
* if {@code true}, scan for nested property definitions as well
*
* @since 8.10
*/
public BeanValidationBinder(Class<BEAN> beanType, boolean scanNestedDefinitions) {
public BeanValidationBinder(Class<BEAN> beanType,
boolean scanNestedDefinitions) {
super(beanType, scanNestedDefinitions);
if (!BeanUtil.checkBeanValidationAvailable()) {
throw new IllegalStateException(BeanValidationBinder.class

+ 135
- 19
server/src/main/java/com/vaadin/data/Binder.java View File

@@ -228,28 +228,51 @@ public class Binder<BEAN> implements Serializable {
public Setter<BEAN, TARGET> getSetter();

/**
* Enable or disable asRequired validator.
* The validator is enabled by default.
* Enable or disable asRequired validator. The validator is enabled by
* default.
*
* @see #asRequired(String)
* @see #asRequired(ErrorMessageProvider)
*
* @param asRequiredEnabled
* {@code false} if asRequired validator should
* be disabled, {@code true} otherwise (default)
* {@code false} if asRequired validator should be disabled,
* {@code true} otherwise (default)
*
* @since 8.10
*/
public void setAsRequiredEnabled(boolean asRequiredEnabled);

/**
* Returns whether asRequired validator is currently enabled or not
* Returns whether asRequired validator is currently enabled or not.
*
* @see #asRequired(String)
* @see #asRequired(ErrorMessageProvider)
*
* @return {@code false} if asRequired validator is disabled
* {@code true} otherwise (default)
*
* @since 8.10
*/
public boolean isAsRequiredEnabled();

/**
* Define whether validators are disabled or enabled for this
* specific binding.
*
* @param validatorsDisabled A boolean value
*
* @since 8.11
*/
public void setValidatorsDisabled(boolean validatorsDisabled);

/**
* Returns if validators are currently disabled or not
*
* @return A boolean value
*
* @since 8.11
*/
public boolean isValidatorsDisabled();
}

/**
@@ -796,6 +819,7 @@ public class Binder<BEAN> implements Serializable {
private final HasValue<FIELDVALUE> field;
private BindingValidationStatusHandler statusHandler;
private boolean isStatusHandlerChanged;
private Binding<BEAN, TARGET> binding;

private boolean bound;

@@ -851,6 +875,7 @@ public class Binder<BEAN> implements Serializable {

bound = true;
getBinder().incompleteBindings.remove(getField());
this.binding = binding;

return binding;
}
@@ -885,6 +910,7 @@ public class Binder<BEAN> implements Serializable {
Binding binding = ((BindingBuilder) finalBinding).bind(getter,
setter);
getBinder().boundProperties.put(propertyName, binding);
this.binding = binding;
return binding;
} finally {
getBinder().incompleteMemberFieldBindings.remove(getField());
@@ -905,8 +931,17 @@ public class Binder<BEAN> implements Serializable {
checkUnbound();
Objects.requireNonNull(validator, "validator cannot be null");

Validator<? super TARGET> wrappedValidator = ((value, context) -> {
if (getBinder().isValidatorsDisabled() ||
(binding != null && binding.isValidatorsDisabled())) {
return ValidationResult.ok();
} else {
return validator.apply(value, context);
}
});

converterValidatorChain = ((Converter<FIELDVALUE, TARGET>) converterValidatorChain)
.chain(new ValidatorAsConverter<>(validator));
.chain(new ValidatorAsConverter<>(wrappedValidator));
return this;
}

@@ -946,10 +981,11 @@ public class Binder<BEAN> implements Serializable {
this.asRequiredSet = true;
field.setRequiredIndicatorVisible(true);
return withValidator((value, context) -> {
if (!field.isRequiredIndicatorVisible())
if (!field.isRequiredIndicatorVisible()) {
return ValidationResult.ok();
else
} else {
return customRequiredValidator.apply(value, context);
}
});
}

@@ -1054,6 +1090,8 @@ public class Binder<BEAN> implements Serializable {

private boolean asRequiredSet;

private boolean validatorsDisabled = false;

public BindingImpl(BindingBuilderImpl<BEAN, FIELDVALUE, TARGET> builder,
ValueProvider<BEAN, TARGET> getter,
Setter<BEAN, TARGET> setter) {
@@ -1281,7 +1319,8 @@ public class Binder<BEAN> implements Serializable {
* field doesn't accept null rather than throwing for some other
* reason.
*/
if (convertedValue == null && getField().getEmptyValue() != null) {
if (convertedValue == null
&& getField().getEmptyValue() != null) {
throw new IllegalStateException(String.format(
"A field of type %s didn't accept a null value."
+ " If null values are expected, then configure a null representation for the binding.",
@@ -1322,12 +1361,12 @@ public class Binder<BEAN> implements Serializable {
public void setAsRequiredEnabled(boolean asRequiredEnabled) {
if (!asRequiredSet) {
throw new IllegalStateException(
"Unable to toggle asRequired validation since "
+ "asRequired has not been set.");
"Unable to toggle asRequired validation since "
+ "asRequired has not been set.");
}
if (asRequiredEnabled != isAsRequiredEnabled()) {
field.setRequiredIndicatorVisible(asRequiredEnabled);
validate();
validate();
}
}

@@ -1335,6 +1374,16 @@ public class Binder<BEAN> implements Serializable {
public boolean isAsRequiredEnabled() {
return field.isRequiredIndicatorVisible();
}

@Override
public void setValidatorsDisabled(boolean validatorsDisabled) {
this.validatorsDisabled = validatorsDisabled;
}

@Override
public boolean isValidatorsDisabled() {
return validatorsDisabled;
}
}

/**
@@ -1440,6 +1489,8 @@ public class Binder<BEAN> implements Serializable {

private Set<Binding<BEAN, ?>> changedBindings = new LinkedHashSet<>();

private boolean validatorsDisabled = false;

/**
* Creates a binder using a custom {@link PropertySet} implementation for
* finding and resolving property names for
@@ -1732,7 +1783,7 @@ public class Binder<BEAN> implements Serializable {
* <p>
* After updating each field, the value is read back from the field and the
* bean's property value is updated if it has been changed from the original
* value by the field or a converter.
* value by the field or a converter.
*
* @see #readBean(Object)
* @see #writeBean(Object)
@@ -1799,8 +1850,9 @@ public class Binder<BEAN> implements Serializable {
// avoid NPE inside initFieldValue. It happens e.g. when
// we unbind a binding in valueChangeListener of another
// field.
if (binding.getField() != null)
if (binding.getField() != null) {
binding.initFieldValue(bean, false);
}
});
getValidationStatusHandler().statusChange(
BinderValidationStatus.createUnresolvedStatus(this));
@@ -1851,9 +1903,32 @@ public class Binder<BEAN> implements Serializable {
* @param bean
* the object to which to write the field values, not
* {@code null}
*
* @since 8.10
*/
public void writeBeanAsDraft(BEAN bean) {
doWriteDraft(bean, new ArrayList<>(bindings));
doWriteDraft(bean, new ArrayList<>(bindings),false);
}

/**
* Writes successfully converted changes from the bound fields bypassing
* all the Validation, or all fields passing conversion if forced = true.
* If the conversion fails, the value written to the bean will be null.
*
* @see #writeBean(Object)
* @see #writeBeanIfValid(Object)
* @see #readBean(Object)
* @see #setBean(Object)
*
* @param bean
* the object to which to write the field values, not
* {@code null}
* @param forced
* disable all Validators during write
* @since 8.11
*/
public void writeBeanAsDraft(BEAN bean, boolean forced) {
doWriteDraft(bean, new ArrayList<>(bindings),forced);
}

/**
@@ -1951,13 +2026,23 @@ public class Binder<BEAN> implements Serializable {
* the bean to write field values into
* @param bindings
* the set of bindings to write to the bean
* @param forced
* disable validators during write if true
*/
@SuppressWarnings({ "unchecked" })
private void doWriteDraft(BEAN bean, Collection<Binding<BEAN, ?>> bindings) {
private void doWriteDraft(BEAN bean,
Collection<Binding<BEAN, ?>> bindings, boolean forced) {
Objects.requireNonNull(bean, "bean cannot be null");

bindings.forEach(binding -> ((BindingImpl<BEAN, ?, ?>) binding)
if (!forced) {
bindings.forEach(binding -> ((BindingImpl<BEAN, ?, ?>) binding)
.writeFieldValue(bean));
} else {
boolean isDisabled = isValidatorsDisabled();
setValidatorsDisabled(true);
bindings.forEach(binding -> ((BindingImpl<BEAN, ?, ?>) binding)
.writeFieldValue(bean));
setValidatorsDisabled(isDisabled);
}
}

/**
@@ -2027,7 +2112,14 @@ public class Binder<BEAN> implements Serializable {
*/
public Binder<BEAN> withValidator(Validator<? super BEAN> validator) {
Objects.requireNonNull(validator, "validator cannot be null");
validators.add(validator);
Validator<? super BEAN> wrappedValidator = ((value, context) -> {
if (isValidatorsDisabled()) {
return ValidationResult.ok();
} else {
return validator.apply(value, context);
}
});
validators.add(wrappedValidator);
return this;
}

@@ -3025,6 +3117,30 @@ public class Binder<BEAN> implements Serializable {
.ifPresent(Binding::unbind);
}

/**
* Control whether validators including bean level validators are
* disabled or enabled globally for this Binder.
*
* @param validatorsDisabled Boolean value
*
* @since 8.11
*/
public void setValidatorsDisabled(boolean validatorsDisabled) {
this.validatorsDisabled = validatorsDisabled;
}

/**
* Returns if the validators including bean level validators
* are disabled or enabled for this Binder.
*
* @return Boolean value
*
* @since 8.11
*/
public boolean isValidatorsDisabled() {
return validatorsDisabled;
}

private static final Logger getLogger() {
return Logger.getLogger(Binder.class.getName());
}

+ 2
- 1
server/src/main/java/com/vaadin/data/RequiredFieldConfigurator.java View File

@@ -50,7 +50,8 @@ public interface RequiredFieldConfigurator
*/
public RequiredFieldConfigurator NOT_EMPTY = annotation -> annotation
.annotationType().getName()
.equals("org.hibernate.validator.constraints.NotEmpty");
.equals("org.hibernate.validator.constraints.NotEmpty") || annotation
.annotationType().getName().equals("javax.validation.constraints.NotEmpty");

/**
* Configurator which is aware of {@literal Size} annotation with

+ 0
- 5
server/src/main/java/com/vaadin/data/ValueContext.java View File

@@ -17,7 +17,6 @@ package com.vaadin.data;

import java.io.Serializable;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

import com.vaadin.ui.Component;
@@ -67,8 +66,6 @@ public class ValueContext implements Serializable {
*/
@SuppressWarnings("unchecked")
public ValueContext(Component component) {
Objects.requireNonNull(component,
"Component can't be null in ValueContext construction");
this.component = component;
if (component instanceof HasValue) {
hasValue = (HasValue<?>) component;
@@ -88,8 +85,6 @@ public class ValueContext implements Serializable {
* @since 8.1
*/
public ValueContext(Component component, HasValue<?> hasValue) {
Objects.requireNonNull(component,
"Component can't be null in ValueContext construction");
this.component = component;
this.hasValue = hasValue;
locale = findLocale();

+ 2
- 2
server/src/main/java/com/vaadin/data/provider/HierarchicalDataCommunicator.java View File

@@ -381,11 +381,11 @@ public class HierarchicalDataCommunicator<T> extends DataCommunicator<T> {
}

/**
* Returns parent index for the row or {@code null}.
* Returns parent index for the row or a negative value.
*
* @param item
* the item to find the parent of
* @return the parent index or {@code null} for top-level items
* @return the parent index or a negative value for top-level items
*/
public Integer getParentIndex(T item) {
return mapper.getParentIndex(item);

+ 13
- 2
server/src/main/java/com/vaadin/data/provider/TreeDataProvider.java View File

@@ -172,9 +172,20 @@ public class TreeDataProvider<T>

private Stream<T> getFilteredStream(Stream<T> stream,
Optional<SerializablePredicate<T>> queryFilter) {
final Optional<SerializablePredicate<T>> combinedFilter;
if (filter != null) {
stream = stream.filter(filter);
combinedFilter = Optional
.of(queryFilter.map(filter::and).orElse(filter));
} else {
combinedFilter = queryFilter;
}
return queryFilter.map(stream::filter).orElse(stream);
return combinedFilter.map(
f -> stream.filter(element -> flatten(element).anyMatch(f)))
.orElse(stream);
}

private Stream<T> flatten(T element) {
return Stream.concat(Stream.of(element), getTreeData()
.getChildren(element).stream().flatMap(this::flatten));
}
}

+ 5
- 0
server/src/main/java/com/vaadin/navigator/Navigator.java View File

@@ -900,6 +900,11 @@ public class Navigator implements Serializable {
/**
* Returns the current navigation state reported by this Navigator's
* {@link NavigationStateManager}.
* <p>
* When the navigation is triggered by the browser (for example by pressing
* the back or forward button in the browser), the navigation state may
* already have been updated to reflect the new address, before the
* {@link #navigateTo(String)} is notified.
*
* @return The navigation state.
*/

+ 2
- 4
server/src/main/java/com/vaadin/server/BootstrapHandler.java View File

@@ -510,8 +510,7 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {

Document document = response.getDocument();

DocumentType doctype = new DocumentType("html", "", "",
document.baseUri());
DocumentType doctype = new DocumentType("html", "", "");
document.child(0).before(doctype);

Element head = document.head();
@@ -712,8 +711,7 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
appendMainScriptTagContents(context, builder);

builder.append("//]]>");
mainScriptTag.appendChild(
new DataNode(builder.toString(), mainScriptTag.baseUri()));
mainScriptTag.appendChild(new DataNode(builder.toString()));
fragmentNodes.add(mainScriptTag);

}

+ 14
- 0
server/src/main/java/com/vaadin/server/Sizeable.java View File

@@ -254,6 +254,20 @@ public interface Sizeable extends Serializable {
*/
public void setSizeFull();

/**
* Sets the width to 100%.
*
* @since 8.11
*/
public void setWidthFull();

/**
* Sets the height to 100%.
*
* @since 8.11
*/
public void setHeightFull();

/**
* Clears any size settings.
*/

+ 18
- 6
server/src/main/java/com/vaadin/server/VaadinService.java View File

@@ -202,7 +202,7 @@ public abstract class VaadinService implements Serializable {
* @since 8.2
*/
protected VaadinService() {
this.deploymentConfiguration = null;
deploymentConfiguration = null;
}

/**
@@ -1226,13 +1226,17 @@ public abstract class VaadinService implements Serializable {
}

/**
* Called at the end of a request, after sending the response. Closes
* inactive UIs in the given session, removes closed UIs from the session,
* and closes the session if it is itself inactive.
* Closes inactive UIs in the given session, removes closed UIs from the
* session, and closes the session if it is itself inactive. This operation
* should not be performed without first acquiring the session lock. By
* default called at the end of each request, after sending the response.
*
* @param session
* the session to clean up
*
* @since 8.10
*/
void cleanupSession(VaadinSession session) {
public void cleanupSession(VaadinSession session) {
if (isSessionActive(session)) {
closeInactiveUIs(session);
removeClosedUIs(session);
@@ -1755,7 +1759,15 @@ public abstract class VaadinService implements Serializable {
* endless loop. This can at least happen if refreshing a
* resource when the session has expired.
*/
response.sendError(HttpServletResponse.SC_GONE,

// Ensure that the browser does not cache expired responses.
// iOS 6 Safari requires this (#3226)
response.setHeader("Cache-Control", "no-cache");
// If Content-Type is not set, browsers assume text/html and may
// complain about the empty response body (#4167)
response.setHeader("Content-Type", "text/plain");

response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Session expired");
}
} catch (IOException e) {

+ 6
- 7
server/src/main/java/com/vaadin/server/communication/HeartbeatHandler.java View File

@@ -62,10 +62,10 @@ public class HeartbeatHandler extends SynchronizedRequestHandler
if (ui != null) {
ui.setLastHeartbeatTimestamp(System.currentTimeMillis());
// Ensure that the browser does not cache heartbeat responses.
// iOS 6 Safari requires this (#10370)
// iOS 6 Safari requires this (#3226)
response.setHeader("Cache-Control", "no-cache");
// If Content-Type is not set, browsers assume text/html and may
// complain about the empty response body (#12182)
// complain about the empty response body (#4167)
response.setHeader("Content-Type", "text/plain");
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND,
@@ -88,15 +88,14 @@ public class HeartbeatHandler extends SynchronizedRequestHandler
if (!ServletPortletHelper.isHeartbeatRequest(request)) {
return false;
}

// Ensure that the browser does not cache expired response.
// iOS 6 Safari requires this (#10370)
// Ensure that the browser does not cache expired heartbeat responses.
// iOS 6 Safari requires this (#3226)
response.setHeader("Cache-Control", "no-cache");
// If Content-Type is not set, browsers assume text/html and may
// complain about the empty response body (#12182)
// complain about the empty response body (#4167)
response.setHeader("Content-Type", "text/plain");

response.sendError(HttpServletResponse.SC_NOT_FOUND, "Session expired");
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Session expired");
return true;
}
}

+ 6
- 9
server/src/main/java/com/vaadin/server/communication/PushHandler.java View File

@@ -198,18 +198,16 @@ public class PushHandler {
* the atmosphere resource for the current request
* @param callback
* the push callback to call when a UI is found and locked
* @param websocket
* true if this is a websocket message (as opposed to a HTTP
* request)
*/
private void callWithUi(final AtmosphereResource resource,
final PushEventCallback callback, boolean websocket) {
final PushEventCallback callback) {
AtmosphereRequest req = resource.getRequest();
VaadinServletRequest vaadinRequest = new VaadinServletRequest(req,
service);
VaadinSession session = null;

if (websocket) {
boolean isWebsocket = resource.transport() == TRANSPORT.WEBSOCKET;
if (isWebsocket) {
// For any HTTP request we have already started the request in the
// servlet
service.requestStart(vaadinRequest, null);
@@ -281,7 +279,7 @@ public class PushHandler {
}
} finally {
try {
if (websocket) {
if (isWebsocket) {
service.requestEnd(vaadinRequest, null, session);
}
} catch (Exception e) {
@@ -520,7 +518,7 @@ public class PushHandler {
* The related atmosphere resources
*/
void onConnect(AtmosphereResource resource) {
callWithUi(resource, establishCallback, false);
callWithUi(resource, establishCallback);
}

/**
@@ -531,8 +529,7 @@ public class PushHandler {
* The related atmosphere resources
*/
void onMessage(AtmosphereResource resource) {
callWithUi(resource, receiveCallback,
resource.transport() == TRANSPORT.WEBSOCKET);
callWithUi(resource, receiveCallback);
}

/**

+ 20
- 2
server/src/main/java/com/vaadin/ui/AbstractComponent.java View File

@@ -30,8 +30,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;

import com.vaadin.annotations.Theme;
import com.vaadin.ui.themes.ValoTheme;
import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Element;
@@ -837,6 +835,26 @@ public abstract class AbstractComponent extends AbstractClientConnector
setHeight(100, Unit.PERCENTAGE);
}

/*
* (non-Javadoc)
*
* @see com.vaadin.server.Sizeable#setWidthFull()
*/
@Override
public void setWidthFull() {
setWidth(100, Unit.PERCENTAGE);
}

/*
* (non-Javadoc)
*
* @see com.vaadin.server.Sizeable#setHeightFull()
*/
@Override
public void setHeightFull() {
setHeight(100, Unit.PERCENTAGE);
}

/*
* (non-Javadoc)
*

+ 119
- 21
server/src/main/java/com/vaadin/ui/AbstractDateField.java View File

@@ -178,6 +178,30 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
}
};

/**
* The default start year (inclusive) from which to calculate the
* daylight-saving time zone transition dates.
*/
private static final int DEFAULT_START_YEAR = 1980;

/**
* The default value of the number of future years from the current date for
* which the daylight-saving time zone transition dates are calculated.
*/
private static final int DEFAULT_YEARS_FROM_NOW = 20;

/**
* The optional user-supplied start year (inclusive) from which to calculate
* the daylight-saving time zone transition dates.
*/
private Integer startYear;

/**
* The optional user-supplied end year (inclusive) until which to calculate
* the daylight-saving time zone transition dates.
*/
private Integer endYear;

/**
* Value of the field.
*/
@@ -296,6 +320,11 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* date (taking the resolution into account), the component will not
* validate. If {@code startDate} is set to {@code null}, any value before
* {@code endDate} will be accepted by the range
* <p>
* Note: Negative, i.e. BC dates are not supported.
* <p>
* Note: It's usually recommended to use only one of the following at the same
* time: Range validator with Binder or DateField's setRangeStart check.
*
* @param startDate
* - the allowed range's start date
@@ -357,6 +386,9 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
* date (taking the resolution into account), the component will not
* validate. If {@code endDate} is set to {@code null}, any value after
* {@code startDate} will be accepted by the range.
* <p>
* Note: It's usually recommended to use only one of the following at the same
* time: Range validator with Binder or DateField's setRangeEnd check.
*
* @param endDate
* the allowed range's end date (inclusive, based on the current
@@ -481,7 +513,7 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &

/**
* Sets the {@link ZoneId}, which is used when {@code z} is included inside
* the {@link #setDateFormat(String)}.
* the {@link #setDateFormat(String)} .
*
* @param zoneId
* the zone id
@@ -490,27 +522,82 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
public void setZoneId(ZoneId zoneId) {
if (zoneId != this.zoneId
|| (zoneId != null && !zoneId.equals(this.zoneId))) {
updateTimeZoneJSON(zoneId, getLocale());
updateTimeZoneJSON(zoneId, getLocale(), getStartYear(),
getEndYear());
}
this.zoneId = zoneId;
}

private void updateTimeZoneJSON(ZoneId zoneId, Locale locale) {
private void updateTimeZoneJSON(ZoneId zoneId, Locale locale, int startYear,
int endYear) {
String timeZoneJSON;
if (zoneId != null && locale != null) {
timeZoneJSON = TimeZoneUtil.toJSON(zoneId, locale);
timeZoneJSON = TimeZoneUtil.toJSON(zoneId, locale, startYear,
endYear);
} else {
timeZoneJSON = null;
}
getState().timeZoneJSON = timeZoneJSON;
}

/**
* Sets {@link startYear} and {@link endYear}: the start and end years (both
* inclusive) between which to calculate the daylight-saving time zone
* transition dates. Both parameters are used when '{@code z}' is included
* inside the {@link #setDateFormat(String)}, they would have no effect
* otherwise. Specifically, these parameters determine the range of years in
* which zone names are are adjusted to show the daylight saving names.
*
* If no values are provided, by default {@link startYear} is set to
* {@value #DEFAULT_START_YEAR}, and {@link endYear} is set to
* {@value #DEFAULT_YEARS_FROM_NOW} years into the future from the current
* date.
*
* @param startYear
* the start year of DST transitions
* @param endYear
* the end year of DST transitions
* @since 8.11
*/
public void setDaylightSavingTimeRange(int startYear, int endYear) {
if (startYear > endYear) {
throw new IllegalArgumentException(
"The start year from which to begin calculating the "
+ "daylight-saving time zone transition dates must"
+ " be less than or equal to the end year.\n"
+ startYear + " is greater than " + endYear);
}
if (this.startYear == null || this.endYear == null
|| startYear != this.startYear || endYear != this.endYear) {
updateTimeZoneJSON(getZoneId(), getLocale(), startYear, endYear);
}
this.startYear = startYear;
this.endYear = endYear;
}

private int getStartYear() {
if (startYear == null) {
return DEFAULT_START_YEAR;
} else {
return startYear;
}
}

private int getEndYear() {
if (endYear == null) {
return LocalDate.now().getYear() + DEFAULT_YEARS_FROM_NOW;
} else {
return endYear;
}
}

@Override
public void setLocale(Locale locale) {
Locale oldLocale = getLocale();
if (locale != oldLocale
|| (locale != null && !locale.equals(oldLocale))) {
updateTimeZoneJSON(getZoneId(), locale);
updateTimeZoneJSON(getZoneId(), locale, getStartYear(),
getEndYear());
}
super.setLocale(locale);
}
@@ -616,27 +703,38 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
*
* @param value
* the new value, may be {@code null}
* @throws IllegalArgumentException
* if the value is not within range bounds
*/
@Override
public void setValue(T value) {
currentErrorMessage = null;
/*
* First handle special case when the client side component have a date
* string but value is null (e.g. unparsable date string typed in by the
* user). No value changes should happen, but we need to do some
* internal housekeeping.
*/
if (value == null && !getState(false).parsable) {
RangeValidator<T> validator = getRangeValidator();
ValidationResult result = validator.apply(value,
new ValueContext(this, this));

if (result.isError()) {
throw new IllegalArgumentException(
"value is not within acceptable range");
} else {
currentErrorMessage = null;
/*
* Side-effects of doSetValue clears possible previous strings and
* flags about invalid input.
* First handle special case when the client side component has a date
* string but value is null (e.g. unparsable date string typed in by the
* user). No value changes should happen, but we need to do some
* internal housekeeping.
*/
doSetValue(null);

markAsDirty();
return;
if (value == null && !getState(false).parsable) {
/*
* Side-effects of doSetValue clears possible previous strings and
* flags about invalid input.
*/
doSetValue(null);

markAsDirty();
return;
}
super.setValue(value);
}
super.setValue(value);
}

/**
@@ -783,8 +881,8 @@ public abstract class AbstractDateField<T extends Temporal & TemporalAdjuster &
@Override
protected void doSetValue(T value) {

this.value = value;
// Also set the internal dateString
this.value = value;
if (value == null) {
value = getEmptyValue();
}

+ 6
- 4
server/src/main/java/com/vaadin/ui/ConnectorTracker.java View File

@@ -878,10 +878,12 @@ public class ConnectorTracker implements Serializable {
}
Map<String, StreamVariable> nameToStreamVar = pidToNameToStreamVariable
.get(connectorId);
StreamVariable streamVar = nameToStreamVar.remove(variableName);
streamVariableToSeckey.remove(streamVar);
if (nameToStreamVar.isEmpty()) {
pidToNameToStreamVariable.remove(connectorId);
if (nameToStreamVar != null) {
StreamVariable streamVar = nameToStreamVar.remove(variableName);
streamVariableToSeckey.remove(streamVar);
if (nameToStreamVar.isEmpty()) {
pidToNameToStreamVariable.remove(connectorId);
}
}
}


+ 9
- 3
server/src/main/java/com/vaadin/ui/Grid.java View File

@@ -1948,13 +1948,19 @@ public class Grid<T> extends AbstractListing<T> implements HasComponents,
* @param editable
* {@code true} if column is editable; {@code false} if not
* @return this column
* @throws IllegalStateException
* if editable is true and column has no editor binding or
* component defined
*
* @see #setEditorComponent(HasValue, Setter)
* @see #setEditorBinding(Binding)
*/
public Column<T, V> setEditable(boolean editable) {
Objects.requireNonNull(editorBinding,
"Column has no editor binding or component defined");
public Column<T, V> setEditable(boolean editable)
throws IllegalStateException {
if (editable && editorBinding == null) {
throw new IllegalStateException(
"Column has no editor binding or component defined");
}
getState().editable = editable;
return this;
}

+ 12
- 0
server/src/main/java/com/vaadin/ui/Tree.java View File

@@ -573,6 +573,18 @@ public class Tree<T> extends Composite
public Registration addSelectionListener(SelectionListener<T> listener) {
return treeGrid.addSelectionListener(listener);
}
/**
* Use this tree as a multi select in {@link Binder}. Throws
* {@link IllegalStateException} if the tree is not using
* {@link SelectionMode#MULTI}.
*
* @return the multi select wrapper that can be used in binder
* @since 8.11
*/
public MultiSelect<T> asMultiSelect() {
return treeGrid.asMultiSelect();
}

/**
* Use this tree as a single select in {@link Binder}. Throws

+ 5
- 4
server/src/main/java/com/vaadin/ui/UI.java View File

@@ -1588,10 +1588,11 @@ public abstract class UI extends AbstractSingleComponentContainer
* accessSynchronously. Furthermore, there wasn't an
* ErrorHandlingRunnable that handled the exception.
*/
getLogger().log(Level.WARNING,
"access() task ignored because UI got detached after the task was enqueued."
+ " To suppress this message, change the task to implement {} and make it handle {}."
+ " Affected task: {}",
getLogger().log(Level.WARNING, "access() task ignored "
+ "because UI got detached after the task was "
+ "enqueued. To suppress this message, change "
+ "the task to implement {0} and make it handle "
+ "{1}. Affected task: {2}",
new Object[] {
ErrorHandlingRunnable.class.getName(),
UIDetachedException.class.getName(),

+ 60
- 35
server/src/main/java/com/vaadin/ui/Upload.java View File

@@ -98,10 +98,6 @@ public class Upload extends AbstractComponent

private int totalBytes;

private String buttonCaption = "Upload";

private String buttonStyleName;

/**
* ProgressListeners to which information about progress is sent during
* upload
@@ -184,13 +180,6 @@ public class Upload extends AbstractComponent

target.addAttribute("state", isUploading);

if (buttonCaption != null) {
target.addAttribute("buttoncaption", buttonCaption);
if (buttonStyleName != null) {
target.addAttribute("buttonstylename", buttonStyleName);
}
}

target.addAttribute("nextid", nextid);

// Post file to this stream variable
@@ -982,17 +971,31 @@ public class Upload extends AbstractComponent
* @return String to be rendered into button that fires uploading
*/
public String getButtonCaption() {
return buttonCaption;
return getState(false).buttonCaption;
}

/**
* Returns the stylename rendered into button that fires uploading.
* Returns the style name rendered into button that fires uploading.
*
* @return Stylename to be rendered into button that fires uploading
* @return Style name to be rendered into button that fires uploading
* @since 8.2
*/
public String getButtonStyleName() {
return buttonStyleName;
return getState(false).buttonStyleName;
}

/**
* Checks whether the caption of the button that fires uploading is rendered
* as HTML
* <p>
* The default is {@code false}, i.e. to render that caption as plain text.
*
* @return {@code true} if the caption is rendered as HTML, {@code false} if
* rendered as plain text
* @since 8.11
*/
public boolean isButtonCaptionAsHtml() {
return getState(false).buttonCaptionAsHtml;
}

/**
@@ -1005,8 +1008,8 @@ public class Upload extends AbstractComponent
* {@link #submitUpload()}.
* <p>
* In case the Upload is used in immediate mode using
* {@link #setImmediateMode(boolean)}, the file choose (html input with type
* "file") is hidden and only the button with this text is shown.
* {@link #setImmediateMode(boolean)}, the file chooser (HTML input with
* type "file") is hidden and only the button with this text is shown.
* <p>
*
* <p>
@@ -1018,41 +1021,63 @@ public class Upload extends AbstractComponent
* text for upload components button.
*/
public void setButtonCaption(String buttonCaption) {
this.buttonCaption = buttonCaption;
markAsDirty();
getState().buttonCaption = buttonCaption;
}

/**
* In addition to the actual file chooser, upload components have button
* that starts actual upload progress. This method is used to set a
* stylename to that button.
* that starts actual upload progress. This method is used to set a style
* name to that button.
* <p>
* Note: Unlike {@code Button.setStyleName()} this method overrides all the
* styles from the button. If you wish to preserve the default styles, enter
* the style name as {@code "v-button yourStyleName"}.
*
* @param buttonStyleName
* styleName for upload components button.
* style name for upload components button.
* @see #setButtonCaption(String) about when the button is shown / hidden.
* @since 8.2
*/
public void setButtonStyleName(String buttonStyleName) {
this.buttonStyleName = buttonStyleName;
markAsDirty();
getState().buttonStyleName = buttonStyleName;
}

/**
* In addition to the actual file chooser, upload components have button
* that starts actual upload progress. This method is used to set whether
* the caption on that button is rendered as HTML.
* <p>
* If set to {@code true}, the caption is rendered in the browser as HTML
* and the developer is responsible for ensuring no harmful HTML is used. If
* set to {@code false}, the caption is rendered in the browser as plain
* text.
* <p>
* The default is {@code false}, i.e. to render the caption as plain text.
*
* @param buttonCaptionAsHtml
* {@code true} if the caption is rendered as HTML, {@code false}
* if rendered as plain text
* @since 8.11
*/
public void setButtonCaptionAsHtml(boolean buttonCaptionAsHtml) {
getState().buttonCaptionAsHtml = buttonCaptionAsHtml;
}

/**
* Forces the upload the send selected file to the server.
* Instructs the upload component to send selected file to the server.
* <p>
* In case developer wants to use this feature, he/she will most probably
* want to hide the uploads internal submit button by setting its caption to
* null with {@link #setButtonCaption(String)} method.
* want to hide the upload component's internal submit button by setting its
* caption to null with {@link #setButtonCaption(String)} method.
* <p>
* Note, that the upload runs asynchronous. Developer should use normal
* upload listeners to trac the process of upload. If the field is empty
* uploaded the file name will be empty string and file length 0 in the
* upload finished event.
* Note that the upload runs asynchronously. Developer should use normal
* upload listeners to track the process of upload. If the file name field
* is empty, no upload will be triggered.
* <p>
* Also note, that the developer should not remove or modify the upload in
* the same user transaction where the upload submit is requested. The
* upload may safely be hidden or removed once the upload started event is
* fired.
* Also note that the developer should not remove or modify the upload
* component in the same user transaction where the upload submit is
* requested. The upload component can be safely hidden or removed once the
* upload started event has been fired.
*/
public void submitUpload() {
markAsDirty();

+ 5
- 1
server/src/main/java/com/vaadin/ui/components/grid/GridRowDragger.java View File

@@ -381,7 +381,11 @@ public class GridRowDragger<T> implements Serializable {
}

sourceItems.removeAll(droppedItems);
listDataProvider.refreshAll();

// if reordering the same grid, DataProvider's refresh will be done later
if (getGridDragSource().getGrid() != getGridDropTarget().getGrid()) {
listDataProvider.refreshAll();
}
}

private void handleTargetGridDrop(GridDropEvent<T> event, final int index,

+ 2
- 2
server/src/main/java/com/vaadin/ui/declarative/Design.java View File

@@ -234,14 +234,14 @@ public class Design implements Serializable {
throw new DesignException("Unknown tag: " + tagName);
}
String[] classNameParts = parts[1].split("-");
String className = "";
StringBuilder className = new StringBuilder();
for (String classNamePart : classNameParts) {
// Split will ignore trailing and multiple dashes but that
// should be
// ok
// <vaadin-button--> will be resolved to <vaadin-button>
// <vaadin--button> will be resolved to <vaadin-button>
className += SharedUtil.capitalize(classNamePart);
className.append(SharedUtil.capitalize(classNamePart));
}
String qualifiedClassName = packageName + "." + className;


+ 5
- 0
server/src/main/java/com/vaadin/ui/themes/ValoTheme.java View File

@@ -380,6 +380,11 @@ public class ValoTheme {
/**
* Move the default caption icon inside the text field. Can be combined with
* any other TextField style.
* <p>
* Note: Does not apply in FormLayout
* <p>
* Note: TextFields with inlined icons in Grid header need to be wrapped into
* e.g. HorizontalLayout
*/
public static final String TEXTFIELD_INLINE_ICON = "inline-icon";


+ 37
- 5
server/src/main/java/com/vaadin/util/TimeZoneUtil.java View File

@@ -42,13 +42,14 @@ import elemental.json.impl.JsonUtil;
public final class TimeZoneUtil implements Serializable {

/**
* The start year used to send the time zone transition dates.
* The default start year (inclusive) from which to calculate the
* daylight-saving time zone transition dates.
*/
private static final int STARTING_YEAR = 1980;

/**
* Till how many years from now, should we send the time zone transition
* dates.
* The default value of the number of future years from the current date for
* which the daylight-saving time zone transition dates are calculated.
*/
private static final int YEARS_FROM_NOW = 20;

@@ -61,6 +62,12 @@ public final class TimeZoneUtil implements Serializable {
* which is used in
* {@link com.google.gwt.i18n.client.TimeZone#createTimeZone(String)}.
*
* This method calculates the JSON string from the year
* {@value #STARTING_YEAR} until {@value #YEARS_FROM_NOW} years into the
* future from the current date.
*
* @see #toJSON(ZoneId, Locale, int, int)
*
* @param zoneId
* the {@link ZoneId} to get the daylight transitions from
* @param locale
@@ -69,6 +76,32 @@ public final class TimeZoneUtil implements Serializable {
* @return the encoded string
*/
public static String toJSON(ZoneId zoneId, Locale locale) {
int endYear = LocalDate.now().getYear() + YEARS_FROM_NOW;
return toJSON(zoneId, locale, STARTING_YEAR, endYear);
}

/**
* Returns a JSON string of the specified {@code zoneId} and {@link Locale},
* which is used in
* {@link com.google.gwt.i18n.client.TimeZone#createTimeZone(String)}.
*
* This method calculates the JSON string from {@code startYear} until
* {@code startYear}, both inclusive.
*
* @param zoneId
* the {@link ZoneId} to get the daylight transitions from
* @param locale
* the locale used to determine the short name of the time zone
* @param startYear
* the start year of DST transitions
* @param endYear
* the end year of DST transitions
*
* @return the encoded string
* @since 8.11
*/
public static String toJSON(ZoneId zoneId, Locale locale, int startYear,
int endYear) {
if (zoneId == null || locale == null) {
return null;
}
@@ -78,9 +111,8 @@ public final class TimeZoneUtil implements Serializable {

TimeZoneInfo info = new TimeZoneInfo();

int endYear = LocalDate.now().getYear() + YEARS_FROM_NOW;
if (timeZone.useDaylightTime()) {
for (int year = STARTING_YEAR; year <= endYear; year++) {
for (int year = startYear; year <= endYear; year++) {
ZonedDateTime i = LocalDateTime.of(year, 1, 1, 0, 0)
.atZone(zoneId);
while (true) {

+ 77
- 2
server/src/test/java/com/vaadin/data/BinderTest.java View File

@@ -274,8 +274,11 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
Binder<Person> binder = new Binder<>();
binder.forField(nameField)
.withValidator((value,context) -> {
if (value.equals("Mike")) return ValidationResult.ok();
else return ValidationResult.error("value must be Mike");
if (value.equals("Mike")) {
return ValidationResult.ok();
} else {
return ValidationResult.error("value must be Mike");
}
})
.bind(Person::getFirstName, Person::setFirstName);
binder.forField(ageField)
@@ -300,6 +303,78 @@ public class BinderTest extends BinderTestBase<Binder<Person>, Person> {
// age is written to draft even if firstname validation
// fails
assertEquals(age, person.getAge());

binder.writeBeanAsDraft(person,true);
// name is now written despite validation as write was forced
assertEquals(fieldValue, person.getFirstName());
}

@Test
public void save_bound_bean_disable_validation_binding() throws ValidationException {
Binder<Person> binder = new Binder<>();
Binding<Person, String> nameBinding = binder.forField(nameField)
.withValidator((value,context) -> {
if (value.equals("Mike")) {
return ValidationResult.ok();
} else {
return ValidationResult.error("value must be Mike");
}
})
.bind(Person::getFirstName, Person::setFirstName);
binder.forField(ageField)
.withConverter(new StringToIntegerConverter(""))
.bind(Person::getAge, Person::setAge);

Person person = new Person();

String fieldValue = "John";
nameField.setValue(fieldValue);

int age = 10;
ageField.setValue("10");

person.setFirstName("Mark");

nameBinding.setValidatorsDisabled(true);
binder.writeBean(person);

// name is now written as validation was disabled
assertEquals(fieldValue, person.getFirstName());
assertEquals(age, person.getAge());
}

@Test
public void save_bound_bean_disable_validation_binder() throws ValidationException {
Binder<Person> binder = new Binder<>();
binder.forField(nameField)
.withValidator((value,context) -> {
if (value.equals("Mike")) {
return ValidationResult.ok();
} else {
return ValidationResult.error("value must be Mike");
}
})
.bind(Person::getFirstName, Person::setFirstName);
binder.forField(ageField)
.withConverter(new StringToIntegerConverter(""))
.bind(Person::getAge, Person::setAge);

Person person = new Person();

String fieldValue = "John";
nameField.setValue(fieldValue);

int age = 10;
ageField.setValue("10");

person.setFirstName("Mark");

binder.setValidatorsDisabled(true);
binder.writeBean(person);

// name is now written as validation was disabled
assertEquals(fieldValue, person.getFirstName());
assertEquals(age, person.getAge());
}

@Test

+ 49
- 4
server/src/test/java/com/vaadin/data/provider/TreeDataProviderTest.java View File

@@ -203,6 +203,51 @@ public class TreeDataProviderTest
assertEquals(stringData.getChildren("a/b"), Arrays.asList());
}

@Test
public void filter_is_applied_to_children_provider_filter() {
final SerializablePredicate<String> dataProviderFilter = item -> item
.contains("Sub");
final HierarchicalQuery<String, SerializablePredicate<String>> query = new HierarchicalQuery<>(
null, null);
filter_is_applied_to_children(dataProviderFilter, query);
}

@Test
public void filter_is_applied_to_children_query_filter() {
final SerializablePredicate<String> dataProviderFilter = null;
final HierarchicalQuery<String, SerializablePredicate<String>> query = new HierarchicalQuery<>(
item -> item.contains("Sub"), null);
filter_is_applied_to_children(dataProviderFilter, query);
}

@Test
public void filter_is_applied_to_children_both_filters() {
final SerializablePredicate<String> dataProviderFilter = item -> item
.contains("Sub");
final HierarchicalQuery<String, SerializablePredicate<String>> query = new HierarchicalQuery<>(
dataProviderFilter, null);
filter_is_applied_to_children(dataProviderFilter, query);
}

private void filter_is_applied_to_children(
final SerializablePredicate<String> dataProviderFilter,
final HierarchicalQuery<String, SerializablePredicate<String>> query) {
final TreeData<String> stringData = new TreeData<>();
final String root1 = "Main";
final List<String> children1 = Arrays.asList("Sub1", "Sub2");
final String root2 = "Other";
final List<String> children2 = Arrays.asList("Foo1", "Foo2");
stringData.addRootItems(root1, root2);
stringData.addItems(root1, children1);
stringData.addItems(root2, children2);
final TreeDataProvider<String> provider = new TreeDataProvider<>(
stringData);
provider.setFilter(dataProviderFilter);
assertEquals("Unexpected amount of root items after filtering.", 1,
provider.getChildCount(query));
assertTrue(provider.fetchChildren(query).allMatch(root1::equals));
}

@Test
public void setFilter() {
getDataProvider().setFilter(item -> item.getValue().equals("Xyz")
@@ -214,7 +259,7 @@ public class TreeDataProviderTest
&& !item.getValue().equals("Xyz"));

assertEquals(
"Previous filter should be replaced when setting a new one", 6,
"Previous filter should be replaced when setting a new one", 14,
sizeWithUnfilteredQuery());

getDataProvider().setFilter(null);
@@ -227,7 +272,7 @@ public class TreeDataProviderTest
public void addFilter() {
getDataProvider().addFilter(item -> item.getId() <= 10);
getDataProvider().addFilter(item -> item.getId() >= 5);
assertEquals(5, sizeWithUnfilteredQuery());
assertEquals(8, sizeWithUnfilteredQuery());
}

@Override
@@ -240,7 +285,7 @@ public class TreeDataProviderTest
.size(new HierarchicalQuery<>("Xyz", null)));
assertEquals("No item should match 'Zyx'", 0, strFilterDataProvider
.size(new HierarchicalQuery<>("Zyx", null)));
assertEquals("Unexpected number of matches for 'Foo'", 3,
assertEquals("Unexpected number of matches for 'Foo'", 4,
strFilterDataProvider
.size(new HierarchicalQuery<>("Foo", null)));
assertEquals("No items should've been filtered out", rootData.size(),
@@ -256,7 +301,7 @@ public class TreeDataProviderTest
assertEquals("No item should match 'Zyx'", 0,
dataProvider.size(new HierarchicalQuery<>(
strBean -> strBean.getValue().contains("Zyx"), null)));
assertEquals("Unexpected number of matches for 'Foo'", 3,
assertEquals("Unexpected number of matches for 'Foo'", 4,
getDataProvider()
.size(new HierarchicalQuery<>(fooFilter, null)));
}

+ 7
- 5
server/src/test/java/com/vaadin/data/provider/hierarchical/HierarchyMapperWithDataTest.java View File

@@ -8,6 +8,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.junit.Before;
@@ -195,11 +196,12 @@ public class HierarchyMapperWithDataTest {
expand(expandedNode);

SerializablePredicate<Node> filter = n -> n.getNumber() % 2 == 0;
List<Node> expectedResult = testData.stream().filter(filter)
.filter(n -> roots.contains(n)
|| n.getParent().equals(testData.get(0))
|| n.getParent().equals(expandedNode))
.collect(Collectors.toList());

// Root nodes plus children of expanded nodes 0 and 4 that match the
// filter
List<Node> expectedResult = IntStream
.of(0, 1, 4, 6, 7, 10, 13, 26, 39, 52).mapToObj(testData::get)
.collect(Collectors.toList());

mapper.setFilter(filter);


+ 20
- 0
server/src/test/java/com/vaadin/tests/server/component/grid/GridRowDraggerOneGridTest.java View File

@@ -127,6 +127,26 @@ public class GridRowDraggerOneGridTest {
verifyDataProvider("1", "2", "0");
}

@Test
public void listDataProvider_calledOnlyOnce() {

final int[] times = new int[1];

source.setItems("0", "1", "2");

source.getDataProvider().addDataProviderListener(ev -> times[0]++);

dragger.setDropIndexCalculator(event -> {
return Integer.MAX_VALUE;
});

drop("1", DropLocation.ABOVE, "0");

verifyDataProvider("1", "2", "0");

Assert.assertArrayEquals("DataProvider should be invoked only once", new int[] { 1 }, times);
}

@Test
public void noopSourceUpdater() {
source.setItems("0", "1", "2");

+ 11
- 2
server/src/test/java/com/vaadin/ui/DateFieldTestCase.java View File

@@ -10,12 +10,17 @@ import java.time.LocalDate;

import org.junit.Before;
import org.junit.Test;
import org.junit.Rule;
import org.junit.rules.ExpectedException;

public class DateFieldTestCase {

private AbstractLocalDateField dateField;
private LocalDate date;

@Rule
public transient ExpectedException exceptionRule = ExpectedException.none();

@Before
public void setup() {
dateField = new AbstractLocalDateField() {
@@ -39,9 +44,11 @@ public class DateFieldTestCase {

@Test
public void belowRangeStartIsNotAcceptedAsValue() {
LocalDate currentDate = dateField.getValue();
dateField.setRangeStart(date);
exceptionRule.expect(IllegalArgumentException.class);
dateField.setValue(date.minusDays(1));
assertNotNull(dateField.getComponentError());
assertThat(dateField.getValue(), is(currentDate));
}

@Test
@@ -60,8 +67,10 @@ public class DateFieldTestCase {

@Test
public void aboveRangeEndIsNotAcceptedAsValue() {
LocalDate currentDate = dateField.getValue();
dateField.setRangeEnd(date);
exceptionRule.expect(IllegalArgumentException.class);
dateField.setValue(date.plusDays(1));
assertNotNull(dateField.getComponentError());
assertThat(dateField.getValue(), is(currentDate));
}
}

+ 1
- 1
shared/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-root</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-shared</artifactId>
<name>vaadin-shared</name>

+ 44
- 4
shared/src/main/java/com/vaadin/shared/ui/dnd/criteria/Criterion.java View File

@@ -116,10 +116,10 @@ public class Criterion implements Serializable {
*/
private Criterion(String key, ComparisonOperator operator, String value,
Payload.ValueType valueType) {
this.key = key;
this.value = value;
this.valueType = valueType;
this.operator = operator;
setKey(key);
setValue(value);
setValueType(valueType);
setOperator(operator);
}

/**
@@ -131,6 +131,16 @@ public class Criterion implements Serializable {
return key;
}

/**
* Sets the key of the payload to be compared.
*
* @param key
* key of the payload to be compared
*/
public void setKey(String key) {
this.key = key;
}

/**
* Gets the value of the payload to be compared.
*
@@ -140,6 +150,16 @@ public class Criterion implements Serializable {
return value;
}

/**
* Sets the value of the payload to be compared.
*
* @param value
* value of the payload to be compared
*/
public void setValue(String value) {
this.value = value;
}

/**
* Gets the type of the payload value to be compared.
*
@@ -149,6 +169,16 @@ public class Criterion implements Serializable {
return valueType;
}

/**
* Sets the type of the payload value to be compared.
*
* @param valueType
* type of the payload to be compared
*/
public void setValueType(Payload.ValueType valueType) {
this.valueType = valueType;
}

/**
* Gets the comparison operator.
*
@@ -158,6 +188,16 @@ public class Criterion implements Serializable {
return operator;
}

/**
* Sets the comparison operator.
*
* @param operator
* comparison operator
*/
public void setOperator(ComparisonOperator operator) {
this.operator = operator;
}

/**
* Compares this criterion's value to the given payload's value and returns
* whether the result matches the criterion's operator. The comparison is

+ 4
- 1
shared/src/main/java/com/vaadin/shared/ui/upload/UploadClientRpc.java View File

@@ -17,10 +17,13 @@ package com.vaadin.shared.ui.upload;

import com.vaadin.shared.communication.ClientRpc;

/**
* Server-to-client RPC interface for Upload.
*/
public interface UploadClientRpc extends ClientRpc {

/**
* Forces the upload the send selected file to the server.
* Instructs the upload component to send the selected file to the server.
*/
void submitUpload();
}

+ 3
- 0
shared/src/main/java/com/vaadin/shared/ui/upload/UploadServerRpc.java View File

@@ -17,6 +17,9 @@ package com.vaadin.shared.ui.upload;

import com.vaadin.shared.communication.ServerRpc;

/**
* Client-to-server RPC interface for Upload.
*/
public interface UploadServerRpc extends ServerRpc {

/**

+ 13
- 0
shared/src/main/java/com/vaadin/shared/ui/upload/UploadState.java View File

@@ -33,7 +33,20 @@ public class UploadState extends AbstractComponentState {
primaryStyleName = "v-upload";
}

/** Upload component's list of accepted content-types. */
@DelegateToWidget
@NoLayout
public String acceptMimeTypes;

/** Caption of the button that fires uploading. */
public String buttonCaption = "Upload";

/** Style name of the button that fires uploading. */
public String buttonStyleName = "v-button";

/**
* Should the caption of the button that fires uploading be rendered as
* HTML.
*/
public boolean buttonCaptionAsHtml;
}

+ 1
- 1
test/addon-using-init-param-widget-set/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-addon-using-init-param-widget-set</artifactId>
<packaging>war</packaging>

+ 1
- 1
test/addon-using-no-defined-widget-set/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-addon-using-no-defined-widget-set</artifactId>
<packaging>war</packaging>

+ 1
- 1
test/addon-using-own-widget-set/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-addon-using-own-widget-set</artifactId>
<packaging>war</packaging>

+ 1
- 1
test/bean-api-validation/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-bean-api-validation</artifactId>
<packaging>jar</packaging>

+ 1
- 1
test/bean-impl-validation/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-bean-impl-validation</artifactId>
<packaging>jar</packaging>

+ 1
- 1
test/cdi/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-cdi</artifactId>
<packaging>war</packaging>

+ 1
- 1
test/default-widget-set/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-default-widget-set</artifactId>
<packaging>war</packaging>

+ 1
- 1
test/dependency-rewrite-addon/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-dependency-rewrite-addon</artifactId>
<packaging>jar</packaging>

+ 1
- 1
test/dependency-rewrite/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-dependency-rewrite</artifactId>
<packaging>war</packaging>

+ 1
- 1
test/own-widget-set/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-own-widget-set</artifactId>
<packaging>war</packaging>

+ 1
- 1
test/pom.xml View File

@@ -5,7 +5,7 @@
<groupId>com.vaadin</groupId>
<artifactId>vaadin-test</artifactId>
<name>vaadin-test</name>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>

<packaging>pom</packaging>
<properties>

+ 1
- 1
test/servlet-containers/generic-tests/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-servlet-containers-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-server-tests</artifactId>
<name>vaadin-test-server-tests</name>

+ 1
- 1
test/servlet-containers/generic-ui/pom.xml View File

@@ -5,7 +5,7 @@
<parent>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-servlet-containers-test</artifactId>
<version>8.10-SNAPSHOT</version>
<version>8.12-SNAPSHOT</version>
</parent>
<artifactId>vaadin-test-server-ui</artifactId>
<name>vaadin-test-server-ui</name>

+ 0
- 0
test/servlet-containers/generic/pom.xml View File


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save