diff options
author | Artur Signell <artur@vaadin.com> | 2015-09-04 15:05:27 +0300 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2015-09-04 15:05:27 +0300 |
commit | f46be1b2792755cb7d9b111068dd6cf202398c4a (patch) | |
tree | 74f7098faee688cc1f1ca1b6ffe9e912338bbcaf | |
parent | e603ea3cf91e5dd0893b766f27d400947527fba9 (diff) | |
parent | ebceef4d44bcd61605fa92fcf7be8d3678537599 (diff) | |
download | vaadin-framework-f46be1b2792755cb7d9b111068dd6cf202398c4a.tar.gz vaadin-framework-f46be1b2792755cb7d9b111068dd6cf202398c4a.zip |
Merge remote-tracking branch 'origin/master' into reconnect-dialog
Change-Id: Ie622160a83116c83b255a26bec297f73f3223ac7
266 files changed, 10286 insertions, 4189 deletions
diff --git a/WebContent/VAADIN/themes/base/button/checkbox.scss b/WebContent/VAADIN/themes/base/button/checkbox.scss index cc6143dbc1..e46d236035 100644 --- a/WebContent/VAADIN/themes/base/button/checkbox.scss +++ b/WebContent/VAADIN/themes/base/button/checkbox.scss @@ -1,9 +1,5 @@ @mixin base-checkbox($primaryStyleName : v-checkbox) { - .#{$primaryStyleName} { - display: block; - } - .#{$primaryStyleName}, .#{$primaryStyleName} label, .#{$primaryStyleName} input, diff --git a/WebContent/VAADIN/themes/base/grid/grid.scss b/WebContent/VAADIN/themes/base/grid/grid.scss index 1653032703..6b3b017070 100644 --- a/WebContent/VAADIN/themes/base/grid/grid.scss +++ b/WebContent/VAADIN/themes/base/grid/grid.scss @@ -108,6 +108,7 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co outline: none; padding: 0 4px; text-align: right; + line-height: 1; &::-moz-focus-inner { border: 0; @@ -128,10 +129,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co &.open { .#{$primaryStyleName}-sidebar-button { width: 100%; - + &:after { - content: "\00d7"; - font-size: 16px; + content: "\f0c9"; + font-size: $v-grid-header-font-size; line-height: 1; } } @@ -142,11 +143,12 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co } .v-ie8 &.open .#{$primaryStyleName}-sidebar-button:after { + vertical-align: middle; + text-align: center; display: inline; } .#{$primaryStyleName}-sidebar-content { - border-top: $v-grid-border; padding: 4px 0; .gwt-MenuBar { @@ -199,6 +201,12 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co border-left: none; } } + + .#{$primaryStyleName}-editor-cells.frozen > div { + @include box-shadow(1px 0 2px rgba(0,0,0,.1)); + border-right: $v-grid-cell-vertical-border; + border-left: none; + } .#{$primaryStyleName}-row-stripe > td { background-color: $v-grid-row-stripe-background-color; @@ -342,6 +350,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co .#{$primaryStyleName}-editor-cells { position: relative; white-space: nowrap; + + &.frozen { + z-index: 2; + } > div { display: inline-block; @@ -384,6 +396,10 @@ $v-grid-details-border-bottom-stripe: 1px solid darken($v-grid-row-background-co min-width: 100%; max-width: 100%; } + + &.not-editable.#{$primaryStyleName}-cell { + float: none; + } } .error::before { diff --git a/WebContent/VAADIN/themes/chameleon/components/components.scss b/WebContent/VAADIN/themes/chameleon/components/components.scss index 9c8a56b33d..578ea23bf3 100644 --- a/WebContent/VAADIN/themes/chameleon/components/components.scss +++ b/WebContent/VAADIN/themes/chameleon/components/components.scss @@ -1,6 +1,7 @@ @import "accordion/accordion.scss"; @import "button/button.scss"; @import "colorpicker/colorpicker.scss"; +@import "grid/grid.scss"; @import "label/label.scss"; @import "menubar/menubar.scss"; @import "notification/notification.scss"; @@ -24,6 +25,7 @@ @include chameleon-accordion; @include chameleon-button; @include chameleon-colorpicker; + @include chameleon-grid; @include chameleon-label; @include chameleon-menubar; @include chameleon-notification; diff --git a/WebContent/VAADIN/themes/chameleon/components/grid/grid.scss b/WebContent/VAADIN/themes/chameleon/components/grid/grid.scss new file mode 100644 index 0000000000..5007ad6619 --- /dev/null +++ b/WebContent/VAADIN/themes/chameleon/components/grid/grid.scss @@ -0,0 +1,12 @@ +@mixin chameleon-grid($primaryStyleName: v-grid) { + + // Sidebar + .#{$primaryStyleName}-sidebar.v-contextmenu { + + .v-on:before, .v-off:before { + content: none; + font-size: 0; + margin-right: 0; + } + } +} diff --git a/WebContent/VAADIN/themes/reindeer/grid/grid.scss b/WebContent/VAADIN/themes/reindeer/grid/grid.scss index 7ae0f402aa..cb5e9d454e 100644 --- a/WebContent/VAADIN/themes/reindeer/grid/grid.scss +++ b/WebContent/VAADIN/themes/reindeer/grid/grid.scss @@ -40,6 +40,12 @@ .#{$primaryStyleName}-sidebar-content { background-color: #f8f8f9; } + + .v-on:before, .v-off:before { + content: none; + font-size: 0; + margin-right: 0; + } } // Sort indicators diff --git a/WebContent/VAADIN/themes/reindeer/reindeer.scss b/WebContent/VAADIN/themes/reindeer/reindeer.scss index cda571fda0..fece9f1043 100644 --- a/WebContent/VAADIN/themes/reindeer/reindeer.scss +++ b/WebContent/VAADIN/themes/reindeer/reindeer.scss @@ -3,16 +3,16 @@ $line-height: normal !default; // Override Base Grid variables -$v-grid-border: 1px solid #c2c3c4; -$v-grid-cell-vertical-border: 1px solid #d4d4d4; -$v-grid-cell-horizontal-border: none; -$v-grid-cell-focused-border: 1px solid #0f68ba; -$v-grid-row-height: 20px; -$v-grid-row-stripe-background-color: #eff0f1; -$v-grid-row-selected-background-color: #4d749f; -$v-grid-header-font-size: 10px; -$v-grid-header-background-color: rgb(217,219,221); -$v-grid-cell-padding-horizontal: 6px; +$v-grid-border: 1px solid #c2c3c4 !default; +$v-grid-cell-vertical-border: 1px solid #d4d4d4 !default; +$v-grid-cell-horizontal-border: none !default; +$v-grid-cell-focused-border: 1px solid #0f68ba !default; +$v-grid-row-height: 20px !default; +$v-grid-row-stripe-background-color: #eff0f1 !default; +$v-grid-row-selected-background-color: #4d749f !default; +$v-grid-header-font-size: 10px !default; +$v-grid-header-background-color: rgb(217,219,221) !default; +$v-grid-cell-padding-horizontal: 6px !default; @import "../base/base.scss"; diff --git a/WebContent/VAADIN/themes/runo/grid/grid.scss b/WebContent/VAADIN/themes/runo/grid/grid.scss index aca9821c53..1f049c5fb0 100644 --- a/WebContent/VAADIN/themes/runo/grid/grid.scss +++ b/WebContent/VAADIN/themes/runo/grid/grid.scss @@ -30,14 +30,7 @@ // Sidebar .#{$primaryStyleName}-sidebar.v-contextmenu { - &.open { - .#{$primaryStyleName}-sidebar-button { - &:after { - font-size: 22px; - } - } - } - + .#{$primaryStyleName}-sidebar-content { background-color: transparent; @@ -45,6 +38,12 @@ border: none; } } + + .v-on:before, .v-off:before { + content: none; + font-size: 0; + margin-right: 0; + } } // Sort indicators diff --git a/WebContent/VAADIN/themes/runo/runo.scss b/WebContent/VAADIN/themes/runo/runo.scss index 73566be8c3..d481476d4c 100644 --- a/WebContent/VAADIN/themes/runo/runo.scss +++ b/WebContent/VAADIN/themes/runo/runo.scss @@ -3,18 +3,18 @@ $line-height: 18px !default; // Override Base Grid variables -$v-grid-border: 1px solid #b6bbbc; -$v-grid-cell-vertical-border: 1px solid #d4d4d4; -$v-grid-cell-vertical-border: none; -$v-grid-cell-horizontal-border: none; -$v-grid-cell-focused-border: 1px solid #57a7ed; -$v-grid-row-height: 26px; -$v-grid-header-row-height: 36px; +$v-grid-border: 1px solid #b6bbbc !default; +$v-grid-cell-vertical-border: 1px solid #d4d4d4 !default; +$v-grid-cell-vertical-border: none !default; +$v-grid-cell-horizontal-border: none !default; +$v-grid-cell-focused-border: 1px solid #57a7ed !default; +$v-grid-row-height: 26px !default; +$v-grid-header-row-height: 36px !default; $v-grid-row-background-color: #fff !default; -$v-grid-row-stripe-background-color:#eff0f1; -$v-grid-row-selected-background-color: #57a7ed; -$v-grid-header-font-size: 15px; -$v-grid-header-background-color: #e7e9ea; +$v-grid-row-stripe-background-color:#eff0f1 !default; +$v-grid-row-selected-background-color: #57a7ed !default; +$v-grid-header-font-size: 15px !default; +$v-grid-header-background-color: #e7e9ea !default; @import "../base/base.scss"; diff --git a/WebContent/VAADIN/themes/tests-tickets/folder with space/resource with special $chars@.txt b/WebContent/VAADIN/themes/tests-tickets/folder with space/resource with special $chars@.txt new file mode 100644 index 0000000000..dff31dd51f --- /dev/null +++ b/WebContent/VAADIN/themes/tests-tickets/folder with space/resource with special $chars@.txt @@ -0,0 +1 @@ +Just ordinary contents here
\ No newline at end of file diff --git a/WebContent/VAADIN/themes/tests-tickets/ordinary.txt b/WebContent/VAADIN/themes/tests-tickets/ordinary.txt new file mode 100644 index 0000000000..dff31dd51f --- /dev/null +++ b/WebContent/VAADIN/themes/tests-tickets/ordinary.txt @@ -0,0 +1 @@ +Just ordinary contents here
\ No newline at end of file diff --git a/WebContent/VAADIN/themes/tests-tickets/percentagein%20name.txt b/WebContent/VAADIN/themes/tests-tickets/percentagein%20name.txt new file mode 100644 index 0000000000..dff31dd51f --- /dev/null +++ b/WebContent/VAADIN/themes/tests-tickets/percentagein%20name.txt @@ -0,0 +1 @@ +Just ordinary contents here
\ No newline at end of file diff --git a/WebContent/VAADIN/themes/valo/components/_grid.scss b/WebContent/VAADIN/themes/valo/components/_grid.scss index 27d421b9f2..e9b4d249c7 100644 --- a/WebContent/VAADIN/themes/valo/components/_grid.scss +++ b/WebContent/VAADIN/themes/valo/components/_grid.scss @@ -21,6 +21,11 @@ $v-grid-details-marker-width: first-number($v-grid-border) * 2 !default; $v-grid-details-marker-color: $v-selection-color !default; $v-grid-details-border-top: valo-border($color: $v-grid-border-color-source, $strength: 0.3) !default; $v-grid-details-border-top-stripe: valo-border($color: $v-grid-row-stripe-background-color, $strength: 0.3) !default; + +$v-grid-border-size: 1px !default; +$v-grid-border: $v-grid-border-size solid #ddd !default; +$v-grid-cell-vertical-border: $v-grid-border !default; +$v-grid-cell-horizontal-border: $v-grid-cell-vertical-border !default; $v-grid-details-border-bottom: $v-grid-cell-horizontal-border !default; $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; @@ -119,6 +124,10 @@ $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; vertical-align: middle; } + &.not-editable.#{$primary-stylename}-cell { + float: none; + } + .error::before { border-top: round($v-unit-size / 4) solid $v-error-indicator-color; border-right: round($v-unit-size / 4) solid transparent; @@ -203,9 +212,6 @@ $v-grid-details-border-bottom-stripe: $v-grid-cell-horizontal-border !default; // Sidebar .#{$primary-stylename}-sidebar.v-contextmenu { &.open { - .#{$primary-stylename}-sidebar-button:after { - font-size: 20px; - } .#{$primary-stylename}-sidebar-content { margin: 0 0 2px; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss b/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss index e97b2fe8d4..c94d48ae14 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/_bourbon.scss @@ -1,6 +1,12 @@ +// Bourbon 3.2.4 +// http://bourbon.io +// Copyright 2011-2015 thoughtbot, inc. +// MIT License + // Settings @import "settings/prefixer"; @import "settings/px-to-em"; +@import "settings/asset-pipeline"; // Custom Helpers @import "helpers/gradient-positions-parser"; @@ -11,12 +17,14 @@ @import "helpers/shape-size-stripper"; // Custom Functions +@import "functions/color-lightness"; @import "functions/flex-grid"; -@import "functions/grid-width"; @import "functions/golden-ratio"; +@import "functions/grid-width"; @import "functions/linear-gradient"; @import "functions/modular-scale"; @import "functions/px-to-em"; +@import "functions/px-to-rem"; @import "functions/radial-gradient"; @import "functions/strip-units"; @import "functions/tint-shade"; @@ -34,8 +42,10 @@ @import "css3/box-sizing"; @import "css3/calc"; @import "css3/columns"; +@import "css3/filter"; @import "css3/flex-box"; @import "css3/font-face"; +@import "css3/font-feature-settings"; @import "css3/hyphens"; @import "css3/hidpi-media-query"; @import "css3/image-rendering"; @@ -56,14 +66,14 @@ @import "addons/ellipsis"; @import "addons/font-family"; @import "addons/hide-text"; -//@import "addons/html5-input-types"; +@import "addons/html5-input-types"; @import "addons/position"; @import "addons/prefixer"; -@import "addons/rem"; @import "addons/retina-image"; @import "addons/size"; @import "addons/timing-functions"; @import "addons/triangle"; +@import "addons/word-wrap"; // Soon to be deprecated Mixins @import "bourbon-deprecated-upcoming"; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss index fcc39fdf35..14a89e480c 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_button.scss @@ -1,38 +1,51 @@ -@mixin button ($style: simple, $base-color: #4294f0) { +@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) { - @if type-of($style) == color { + @if type-of($style) == string and type-of($base-color) == color { + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == string and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: #4294f0; + + @if $padding == inherit { + $padding: 7px 18px; + } + + @include buttonstyle($style, $base-color, $text-size, $padding); + } + + @if type-of($style) == color and type-of($base-color) == color { $base-color: $style; $style: simple; + @include buttonstyle($style, $base-color, $text-size, $padding); } - // Grayscale button - @if $base-color == grayscale($base-color) { - @if $style == simple { - @include simple($base-color, $grayscale: true); - } + @if type-of($style) == color and type-of($base-color) == number { + $padding: $text-size; + $text-size: $base-color; + $base-color: $style; + $style: simple; - @else if $style == shiny { - @include shiny($base-color, $grayscale: true); + @if $padding == inherit { + $padding: 7px 18px; } - @else if $style == pill { - @include pill($base-color, $grayscale: true); - } + @include buttonstyle($style, $base-color, $text-size, $padding); } - // Colored button - @else { - @if $style == simple { - @include simple($base-color); - } + @if type-of($style) == number { + $padding: $base-color; + $text-size: $style; + $base-color: #4294f0; + $style: simple; - @else if $style == shiny { - @include shiny($base-color); + @if $padding == #4294f0 { + $padding: 7px 18px; } - @else if $style == pill { - @include pill($base-color); - } + @include buttonstyle($style, $base-color, $text-size, $padding); } &:disabled { @@ -42,16 +55,55 @@ } +// Selector Style Button +//************************************************************************// +@mixin buttonstyle($type, $b-color, $t-size, $pad) { + // Grayscale button + @if $type == simple and $b-color == grayscale($b-color) { + @include simple($b-color, true, $t-size, $pad); + } + + @if $type == shiny and $b-color == grayscale($b-color) { + @include shiny($b-color, true, $t-size, $pad); + } + + @if $type == pill and $b-color == grayscale($b-color) { + @include pill($b-color, true, $t-size, $pad); + } + + @if $type == flat and $b-color == grayscale($b-color) { + @include flat($b-color, true, $t-size, $pad); + } + + // Colored button + @if $type == simple { + @include simple($b-color, false, $t-size, $pad); + } + + @else if $type == shiny { + @include shiny($b-color, false, $t-size, $pad); + } + + @else if $type == pill { + @include pill($b-color, false, $t-size, $pad); + } + + @else if $type == flat { + @include flat($b-color, false, $t-size, $pad); + } +} + + // Simple Button //************************************************************************// -@mixin simple($base-color, $grayscale: false) { +@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { $color: hsl(0, 0, 100%); $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); - @if lightness($base-color) > 70% { + @if is-light($base-color) { $color: hsl(0, 0, 20%); $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); } @@ -68,10 +120,10 @@ box-shadow: inset 0 1px 0 0 $inset-shadow; color: $color; display: inline-block; - font-size: inherit; + font-size: $textsize; font-weight: bold; @include linear-gradient ($base-color, $stop-gradient); - padding: 7px 18px; + padding: $padding; text-decoration: none; text-shadow: 0 1px 0 $text-shadow; background-clip: padding-box; @@ -92,7 +144,8 @@ @include linear-gradient ($base-color-hover, $stop-gradient-hover); } - &:active:not(:disabled) { + &:active:not(:disabled), + &:focus:not(:disabled) { $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); @@ -102,14 +155,14 @@ } border: 1px solid $border-active; - box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active, 0 1px 1px 0 #eee; + box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active; } } // Shiny Button //************************************************************************// -@mixin shiny($base-color, $grayscale: false) { +@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { $color: hsl(0, 0, 100%); $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); @@ -119,7 +172,7 @@ $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); - @if lightness($base-color) > 70% { + @if is-light($base-color) { $color: hsl(0, 0, 20%); $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); } @@ -140,10 +193,10 @@ box-shadow: inset 0 1px 0 0 $inset-shadow; color: $color; display: inline-block; - font-size: inherit; + font-size: $textsize; font-weight: bold; @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); - padding: 8px 20px; + padding: $padding; text-align: center; text-decoration: none; text-shadow: 0 -1px 1px $text-shadow; @@ -168,21 +221,22 @@ $fourth-stop-hover 100%); } - &:active:not(:disabled) { + &:active:not(:disabled), + &:focus:not(:disabled) { $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); @if $grayscale == true { $inset-shadow-active: grayscale($inset-shadow-active); } - box-shadow: inset 0 0 20px 0 $inset-shadow-active, 0 1px 0 #fff; + box-shadow: inset 0 0 20px 0 $inset-shadow-active; } } // Pill Button //************************************************************************// -@mixin pill($base-color, $grayscale: false) { +@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { $color: hsl(0, 0, 100%); $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); @@ -191,7 +245,7 @@ $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); - @if lightness($base-color) > 70% { + @if is-light($base-color) { $color: hsl(0, 0, 20%); $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); } @@ -208,14 +262,14 @@ border: 1px solid $border-top; border-color: $border-top $border-sides $border-bottom; border-radius: 16px; - box-shadow: inset 0 1px 0 0 $inset-shadow, 0 1px 2px 0 #b3b3b3; + box-shadow: inset 0 1px 0 0 $inset-shadow; color: $color; display: inline-block; - font-size: inherit; + font-size: $textsize; font-weight: normal; line-height: 1; @include linear-gradient ($base-color, $stop-gradient); - padding: 5px 16px; + padding: $padding; text-align: center; text-decoration: none; text-shadow: 0 -1px 1px $text-shadow; @@ -249,7 +303,8 @@ background-clip: padding-box; } - &:active:not(:disabled) { + &:active:not(:disabled), + &:focus:not(:disabled) { $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); @@ -267,7 +322,53 @@ background: $active-color; border: 1px solid $border-active; border-bottom: 1px solid $border-bottom-active; - box-shadow: inset 0 0 6px 3px $inset-shadow-active, 0 1px 0 0 #fff; + box-shadow: inset 0 0 6px 3px $inset-shadow-active; text-shadow: 0 -1px 1px $text-shadow-active; } } + + + +// Flat Button +//************************************************************************// +@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { + $color: hsl(0, 0, 100%); + + @if is-light($base-color) { + $color: hsl(0, 0, 20%); + } + + background-color: $base-color; + border-radius: 3px; + border: none; + color: $color; + display: inline-block; + font-size: inherit; + font-weight: bold; + padding: 7px 18px; + text-decoration: none; + background-clip: padding-box; + + &:hover:not(:disabled){ + $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + } + + background-color: $base-color-hover; + cursor: pointer; + } + + &:active:not(:disabled), + &:focus:not(:disabled) { + $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + + @if $grayscale == true { + $base-color-active: grayscale($base-color-active); + } + + background-color: $base-color-active; + cursor: pointer; + } +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss index 4818f62fd8..742f1031a4 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_directional-values.scss @@ -57,9 +57,6 @@ $right: $pre + "-right" + if($suf, "-#{$suf}", ""); $all: $pre + if($suf, "-#{$suf}", ""); - // Get list inside $vals (is there a better way?) - @each $val in $vals { $vals: $val; } - $vals: collapse-directionals($vals); @if contains-falsy($vals) { @@ -94,21 +91,21 @@ } @mixin margin($vals...) { - @include directional-property(margin, false, $vals); + @include directional-property(margin, false, $vals...); } @mixin padding($vals...) { - @include directional-property(padding, false, $vals); + @include directional-property(padding, false, $vals...); } @mixin border-style($vals...) { - @include directional-property(border, style, $vals); + @include directional-property(border, style, $vals...); } @mixin border-color($vals...) { - @include directional-property(border, color, $vals); + @include directional-property(border, color, $vals...); } @mixin border-width($vals...) { - @include directional-property(border, width, $vals); + @include directional-property(border, width, $vals...); } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss index 26fc879021..8428e4e194 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_html5-input-types.scss @@ -22,7 +22,7 @@ $inputs-list: 'input[type="email"]', $unquoted-inputs-list: (); @each $input-type in $inputs-list { - $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma) !global; + $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma); } $all-text-inputs: $unquoted-inputs-list; @@ -33,7 +33,7 @@ $all-text-inputs: $unquoted-inputs-list; $all-text-inputs-hover: (); @each $input-type in $unquoted-inputs-list { $input-type-hover: $input-type + ":hover"; - $all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma) !global; + $all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma); } // Focus Pseudo-class @@ -41,7 +41,7 @@ $all-text-inputs-hover: (); $all-text-inputs-focus: (); @each $input-type in $unquoted-inputs-list { $input-type-focus: $input-type + ":focus"; - $all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma) !global; + $all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma); } // You must use interpolation on the variable: diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss index aba34edcd9..31a0699769 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_position.scss @@ -14,19 +14,19 @@ position: $position; - @if ($top and $top == auto) or (type-of($top) == number and not unitless($top)) { + @if ($top and $top == auto) or (type-of($top) == number and not(unitless($top))) { top: $top; } - @if ($right and $right == auto) or (type-of($right) == number and not unitless($right)) { + @if ($right and $right == auto) or (type-of($right) == number and not(unitless($right))) { right: $right; } - @if ($bottom and $bottom == auto) or (type-of($bottom) == number and not unitless($bottom)) { + @if ($bottom and $bottom == auto) or (type-of($bottom) == number and not(unitless($bottom))) { bottom: $bottom; } - @if ($left and $left == auto) or (type-of($left) == number and not unitless($left)) { + @if ($left and $left == auto) or (type-of($left) == number and not(unitless($left))) { left: $left; } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_rem.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_rem.scss deleted file mode 100644 index ddd7022b44..0000000000 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_rem.scss +++ /dev/null @@ -1,33 +0,0 @@ -@mixin rem($property, $size, $base: $em-base) { - @if not unitless($base) { - $base: strip-units($base); - } - - $unitless_values: (); - @each $num in $size { - @if not unitless($num) { - @if unit($num) == "em" { - $num: $num * $base; - } - - $num: strip-units($num); - } - - $unitless_values: append($unitless_values, $num); - } - $size: $unitless_values; - - $pixel_values: (); - $rem_values: (); - @each $value in $pxval { - $pixel_value: $value * 1px; - $pixel_values: append($pixel_values, $pixel_value); - - $rem_value: ($value / $base) * 1rem; - $rem_values: append($rem_values, $rem_value); - } - - #{$property}: $pixel_values; - #{$property}: $rem_values; -} - diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss index 7931bd1333..3995c1970a 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_retina-image.scss @@ -1,4 +1,4 @@ -@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: false) { +@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) { @if $asset-pipeline { background-image: image-url("#{$filename}.#{$extension}"); } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss index 51b2410914..5ecc6f9dcf 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_timing-functions.scss @@ -1,5 +1,5 @@ // CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) -// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html +// Timing functions are the same as demo'ed here: http://jqueryui.com/resources/demos/effect/easing.html // EASE IN $ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss index 0e02aca2ca..3b29e2c3c0 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_triangle.scss @@ -2,44 +2,85 @@ height: 0; width: 0; + $width: nth($size, 1); + $height: nth($size, length($size)); + + $foreground-color: nth($color, 1); + $background-color: transparent !default; + @if (length($color) == 2) { + $background-color: nth($color, 2); + } + @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { - border-color: transparent; - border-style: solid; - border-width: $size / 2; + + $width: $width / 2; + $height: if(length($size) > 1, $height, $height/2); @if $direction == up { - border-bottom-color: $color; + border-left: $width solid $background-color; + border-right: $width solid $background-color; + border-bottom: $height solid $foreground-color; } @else if $direction == right { - border-left-color: $color; + border-top: $width solid $background-color; + border-bottom: $width solid $background-color; + border-left: $height solid $foreground-color; } @else if $direction == down { - border-top-color: $color; + border-left: $width solid $background-color; + border-right: $width solid $background-color; + border-top: $height solid $foreground-color; } @else if $direction == left { - border-right-color: $color; + border-top: $width solid $background-color; + border-bottom: $width solid $background-color; + border-right: $height solid $foreground-color; } } @else if ($direction == up-right) or ($direction == up-left) { - border-top: $size solid $color; + border-top: $height solid $foreground-color; @if $direction == up-right { - border-left: $size solid transparent; + border-left: $width solid $background-color; } @else if $direction == up-left { - border-right: $size solid transparent; + border-right: $width solid $background-color; } } @else if ($direction == down-right) or ($direction == down-left) { - border-bottom: $size solid $color; + border-bottom: $height solid $foreground-color; @if $direction == down-right { - border-left: $size solid transparent; + border-left: $width solid $background-color; } @else if $direction == down-left { - border-right: $size solid transparent; + border-right: $width solid $background-color; } } + + @else if ($direction == inset-up) { + border-width: $height $width; + border-style: solid; + border-color: $background-color $background-color $foreground-color; + } + + @else if ($direction == inset-down) { + border-width: $height $width; + border-style: solid; + border-color: $foreground-color $background-color $background-color; + } + + @else if ($direction == inset-right) { + border-width: $width $height; + border-style: solid; + border-color: $background-color $background-color $background-color $foreground-color; + } + + @else if ($direction == inset-left) { + border-width: $width $height; + border-style: solid; + border-color: $background-color $foreground-color $background-color $background-color; + } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/addons/_word-wrap.scss b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_word-wrap.scss new file mode 100644 index 0000000000..9734a597cd --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/addons/_word-wrap.scss @@ -0,0 +1,8 @@ +@mixin word-wrap($wrap: break-word) { + word-wrap: $wrap; + + @if $wrap == break-word { + overflow-wrap: break-word; + word-break: break-all; + } +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss index 42274a4eeb..96f601c1a8 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_columns.scss @@ -15,7 +15,7 @@ @mixin column-fill($arg: auto) { // auto || length - @include prefixer(columns-fill, $arg, webkit moz spec); + @include prefixer(column-fill, $arg, webkit moz spec); } @mixin column-rule($arg) { diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_filter.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_filter.scss new file mode 100644 index 0000000000..8560d77676 --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_filter.scss @@ -0,0 +1,5 @@ +@mixin filter($function: none) { + // <filter-function> [<filter-function]* | none + @include prefixer(filter, $function, webkit spec); +} + diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_flex-box.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_flex-box.scss index b48476e870..34a3a0522b 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_flex-box.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_flex-box.scss @@ -78,7 +78,7 @@ display: flex; } - @elseif $value == "inline-flex" { + @else if $value == "inline-flex" { display: -webkit-inline-box; display: -moz-inline-box; display: inline-box; @@ -124,16 +124,16 @@ $value-2009: horizontal; } - @elseif $value == "row-reverse" { + @else if $value == "row-reverse" { $value-2009: horizontal; $direction: reverse; } - @elseif $value == column { + @else if $value == column { $value-2009: vertical; } - @elseif $value == "column-reverse" { + @else if $value == "column-reverse" { $value-2009: vertical; $direction: reverse; } @@ -162,11 +162,11 @@ $alt-value: single; } - @elseif $value == wrap { + @else if $value == wrap { $alt-value: multiple; } - @elseif $value == "wrap-reverse" { + @else if $value == "wrap-reverse" { $alt-value: multiple; } @@ -224,15 +224,15 @@ $alt-value: start; } - @elseif $value == "flex-end" { + @else if $value == "flex-end" { $alt-value: end; } - @elseif $value == "space-between" { + @else if $value == "space-between" { $alt-value: justify; } - @elseif $value == "space-around" { + @else if $value == "space-around" { $alt-value: center; } @@ -257,7 +257,7 @@ $alt-value: start; } - @elseif $value == "flex-end" { + @else if $value == "flex-end" { $alt-value: end; } @@ -280,7 +280,7 @@ $value-2011: start; } - @elseif $value == "flex-end" { + @else if $value == "flex-end" { $value-2011: end; } @@ -300,15 +300,15 @@ $value-2011: start; } - @elseif $value == "flex-end" { + @else if $value == "flex-end" { $value-2011: end; } - @elseif $value == "space-between" { + @else if $value == "space-between" { $value-2011: justify; } - @elseif $value == "space-around" { + @else if $value == "space-around" { $value-2011: distribute; } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-face.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-face.scss index 029ee8fe88..fbf483fde9 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-face.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-face.scss @@ -1,6 +1,6 @@ // Order of the includes matters, and it is: normal, bold, italic, bold+italic. -@mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: false ) { +@mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: $asset-pipeline) { @font-face { font-family: $font-family; font-weight: $weight; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-feature-settings.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-feature-settings.scss new file mode 100644 index 0000000000..8a9f536775 --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_font-feature-settings.scss @@ -0,0 +1,10 @@ +// Font feature settings mixin and property default. +// Examples: @include font-feature-settings("liga"); +// @include font-feature-settings("lnum" false); +// @include font-feature-settings("pnum" 1, "kern" 0); +// @include font-feature-settings("ss01", "ss02"); + +@mixin font-feature-settings($settings...) { + @if length($settings) == 0 { $settings: none; } + @include prefixer(font-feature-settings, $settings, webkit moz ms spec); +}
\ No newline at end of file diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_image-rendering.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_image-rendering.scss index d4bac3ce0d..03432c637d 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_image-rendering.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_image-rendering.scss @@ -1,6 +1,7 @@ @mixin image-rendering ($mode:auto) { @if ($mode == crisp-edges) { + -ms-interpolation-mode: nearest-neighbor; // IE8+ image-rendering: -moz-crisp-edges; image-rendering: -o-crisp-edges; image-rendering: -webkit-optimize-contrast; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_keyframes.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_keyframes.scss index a9af53da4f..cc12be79bd 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_keyframes.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_keyframes.scss @@ -20,19 +20,16 @@ @content; } } - @if $original-prefix-for-spec { - @include disable-prefix-for-all(); - $prefix-for-spec: true; - // Chrome supports the standard keyframes syntax, but not the standard transform syntax - $prefix-for-webkit: true; - @keyframes #{$name} { - @content; - } - } $prefix-for-webkit: $original-prefix-for-webkit; $prefix-for-mozilla: $original-prefix-for-mozilla; $prefix-for-microsoft: $original-prefix-for-microsoft; $prefix-for-opera: $original-prefix-for-opera; $prefix-for-spec: $original-prefix-for-spec; + + @if $original-prefix-for-spec { + @keyframes #{$name} { + @content; + } + } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_placeholder.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_placeholder.scss index 22fd92b4f2..5682fd097a 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_placeholder.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_placeholder.scss @@ -1,29 +1,8 @@ -$placeholders: '-webkit-input-placeholder', - '-moz-placeholder', - '-ms-input-placeholder'; - @mixin placeholder { + $placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input"; @each $placeholder in $placeholders { - @if $placeholder == "-webkit-input-placeholder" { - &::#{$placeholder} { - @content; - } - } - @else if $placeholder == "-moz-placeholder" { - // FF 18- - &:#{$placeholder} { - @content; - } - - // FF 19+ - &::#{$placeholder} { - @content; - } - } - @else { - &:#{$placeholder} { - @content; - } + &:#{$placeholder}-placeholder { + @content; } } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_transition.scss b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_transition.scss index fe18933fef..5ad4c0aed2 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/css3/_transition.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/css3/_transition.scss @@ -4,13 +4,56 @@ // @include transition-property (transform, opacity); @mixin transition ($properties...) { - @if length($properties) >= 1 { - @include prefixer(transition, $properties, webkit moz spec); + // Fix for vendor-prefix transform property + $needs-prefixes: false; + $webkit: (); + $moz: (); + $spec: (); + + // Create lists for vendor-prefixed transform + @each $list in $properties { + @if nth($list, 1) == "transform" { + $needs-prefixes: true; + $list1: -webkit-transform; + $list2: -moz-transform; + $list3: (); + + @each $var in $list { + $list3: join($list3, $var); + + @if $var != "transform" { + $list1: join($list1, $var); + $list2: join($list2, $var); + } + } + + $webkit: append($webkit, $list1); + $moz: append($moz, $list2); + $spec: append($spec, $list3); + } + + // Create lists for non-prefixed transition properties + @else { + $webkit: append($webkit, $list, comma); + $moz: append($moz, $list, comma); + $spec: append($spec, $list, comma); + } } + @if $needs-prefixes { + -webkit-transition: $webkit; + -moz-transition: $moz; + transition: $spec; + } @else { - $properties: all 0.15s ease-out 0s; - @include prefixer(transition, $properties, webkit moz spec); + @if length($properties) >= 1 { + @include prefixer(transition, $properties, webkit moz spec); + } + + @else { + $properties: all 0.15s ease-out 0s; + @include prefixer(transition, $properties, webkit moz spec); + } } } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_color-lightness.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_color-lightness.scss new file mode 100644 index 0000000000..8c6df4e256 --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_color-lightness.scss @@ -0,0 +1,13 @@ +// Programatically determines whether a color is light or dark +// Returns a boolean +// More details here http://robots.thoughtbot.com/closer-look-color-lightness + +@function is-light($hex-color) { + $-local-red: red(rgba($hex-color, 1.0)); + $-local-green: green(rgba($hex-color, 1.0)); + $-local-blue: blue(rgba($hex-color, 1.0)); + + $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255; + + @return $-local-lightness > .6; +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss index 0a7185916c..afc59eb954 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_modular-scale.scss @@ -1,4 +1,4 @@ -// Scaling Varaibles +// Scaling Variables $golden: 1.618; $minor-second: 1.067; $major-second: 1.125; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_px-to-rem.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_px-to-rem.scss new file mode 100644 index 0000000000..96b244e4cb --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_px-to-rem.scss @@ -0,0 +1,15 @@ +// Convert pixels to rems +// eg. for a relational value of 12px write rem(12) +// Assumes $em-base is the font-size of <html> + +@function rem($pxval) { + @if not unitless($pxval) { + $pxval: strip-units($pxval); + } + + $base: $em-base; + @if not unitless($base) { + $base: strip-units($base); + } + @return ($pxval / $base) * 1rem; +} diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss index 6ceae72102..49e621d63d 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/functions/_transition-property-name.scss @@ -2,21 +2,21 @@ // Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background //************************************************************************// @function transition-property-names($props, $vendor: false) { - $new-props: (); - - @each $prop in $props { - $new-props: append($new-props, transition-property-name($prop, $vendor), comma); - } + $new-props: (); + + @each $prop in $props { + $new-props: append($new-props, transition-property-name($prop, $vendor), comma); + } - @return $new-props; + @return $new-props; } @function transition-property-name($prop, $vendor: false) { - // put other properties that need to be prefixed here aswell - @if $vendor and $prop == transform { - @return unquote('-' + $vendor + '-' + $prop); - } - @else { - @return $prop; - } + // put other properties that need to be prefixed here aswell + @if $vendor and $prop == transform { + @return unquote('-' + $vendor + '-' + $prop); + } + @else { + @return $prop; + } }
\ No newline at end of file diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss b/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss index 5765676838..c145110a17 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/helpers/_render-gradients.scss @@ -16,11 +16,11 @@ } @if $vendor { - $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); + $vendor-gradients: "-#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} #{$gradients})"; } @else if $vendor == false { $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; - $vendor-gradients: unquote($vendor-gradients); } + $vendor-gradients: unquote($vendor-gradients); @return $vendor-gradients; } diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/settings/_asset-pipeline.scss b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_asset-pipeline.scss new file mode 100644 index 0000000000..d481a6afb1 --- /dev/null +++ b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_asset-pipeline.scss @@ -0,0 +1 @@ +$asset-pipeline: false !default; diff --git a/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss index c29a961919..ecab49fb54 100644 --- a/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss +++ b/WebContent/VAADIN/themes/valo/util/bourbon/settings/_prefixer.scss @@ -1,6 +1,6 @@ // Variable settings for /addons/prefixer.scss -$prefix-for-webkit: true; -$prefix-for-mozilla: true; -$prefix-for-microsoft: true; -$prefix-for-opera: true; -$prefix-for-spec: true; // required for keyframe mixin +$prefix-for-webkit: true !default; +$prefix-for-mozilla: true !default; +$prefix-for-microsoft: true !default; +$prefix-for-opera: true !default; +$prefix-for-spec: true !default; // required for keyframe mixin diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index c097fcf1b7..1879175109 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -128,6 +128,16 @@ <async-supported>true</async-supported> </servlet> + <servlet> + <servlet-name>VaadinApplicationRunnerWithPushTimeout</servlet-name> + <servlet-class>com.vaadin.launcher.ApplicationRunnerServlet</servlet-class> + <init-param> + <param-name>pushLongPollingSuspendTimeout</param-name> + <param-value>10000</param-value> + </init-param> + <async-supported>true</async-supported> + </servlet> + <!-- For testing GAE - the deployment script changes this to use GAEVaadinServlet --> <servlet> <servlet-name>IntegrationTest</servlet-name> @@ -173,6 +183,11 @@ </servlet-mapping> <servlet-mapping> + <servlet-name>VaadinApplicationRunnerWithPushTimeout</servlet-name> + <url-pattern>/run-push-timeout/*</url-pattern> + </servlet-mapping> + + <servlet-mapping> <servlet-name>VaadinApplicationRunnerWithJSR356</servlet-name> <url-pattern>/run-jsr356/*</url-pattern> </servlet-mapping> diff --git a/WebContent/release-notes.html b/WebContent/release-notes.html index b7cdba7887..e9b4a24ce1 100644 --- a/WebContent/release-notes.html +++ b/WebContent/release-notes.html @@ -109,11 +109,18 @@ <h3 id="incompatible">Incompatible or Behavior-altering Changes in @version-minor@</h3> <ul> + <li>Window close shortcuts have been changed, and ESCAPE is no longer a hard-coded default, but rather a soft one, + and can be removed. If the <pre>close-shortcut</pre> attribute of the <pre>v-window</pre> element is present, + it must list all close shortcuts, including ESCAPE, separated by whitespace. Existing, unchanged code should + behave as before. See ticket <a href="https://dev.vaadin.com/ticket/17383">#17383</a> for more information + on the reasoning behind the change.</li> <li>The push path has been changed from /PUSH/ to /PUSH to be compatible with JSR 356.</li> <li>Widgetset files and other pre-compressed resources are sent as gzip to compatible browsers. This may interfere with custom response compression solutions that do not respect the Content-Encoding response header.</li> <li>Unused methods related to the "out of sync" message have been removed from SystemMessages class.</li> <li>All notifications use the WAI-ARIA alert role to be compatible with Jaws</li> + <li>Grid SelectionModels are now Extensions. This update removes all selection related variables and API from + GridConnector, GridState, GridServerRpc and GridClientRpc</li> </ul> <h3 id="knownissues">Known Issues and Limitations</h3> <ul> diff --git a/build/ide.xml b/build/ide.xml index 4bd839365f..d468473392 100755 --- a/build/ide.xml +++ b/build/ide.xml @@ -181,7 +181,7 @@ <property name="js.output.dir" location="WebContent" /> <property name="push.js.dir" location="${basedir}/push/result/js" /> <copy todir="${js.output.dir}"> - <fileset dir="${push.js.dir}" includes="VAADIN/vaadinPush*"> + <fileset dir="${push.js.dir}" includes="VAADIN/vaadinPush*" excludes="**/*.gz"> </fileset> </copy> </target> diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java b/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java index b7850e6370..2d08329e9a 100644 --- a/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/AcceptCriteriaFactoryGenerator.java @@ -16,6 +16,7 @@ package com.vaadin.server.widgetsetutils; import java.io.PrintWriter; +import java.util.Arrays; import java.util.Date; import com.google.gwt.core.ext.Generator; @@ -29,6 +30,7 @@ import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; import com.google.gwt.user.rebind.SourceWriter; import com.vaadin.client.ui.dd.VAcceptCriterion; import com.vaadin.client.ui.dd.VAcceptCriterionFactory; +import com.vaadin.server.widgetsetutils.metadata.ConnectorBundle; import com.vaadin.shared.ui.dd.AcceptCriterion; /** @@ -114,7 +116,9 @@ public class AcceptCriteriaFactoryGenerator extends Generator { JClassType criteriaType = context.getTypeOracle().findType( VAcceptCriterion.class.getName()); - for (JClassType clientClass : criteriaType.getSubtypes()) { + JClassType[] subtypes = criteriaType.getSubtypes(); + Arrays.sort(subtypes, ConnectorBundle.jClassComparator); + for (JClassType clientClass : subtypes) { AcceptCriterion annotation = clientClass .getAnnotation(AcceptCriterion.class); if (annotation != null) { diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java index 884852f7c8..2b8ccc87d0 100644 --- a/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/ConnectorBundleLoaderFactory.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; @@ -1185,10 +1186,11 @@ public class ConnectorBundleLoaderFactory extends Generator { JClassType[] types = serverConnectorType.getSubtypes(); - Map<String, JClassType> mappings = new HashMap<String, JClassType>(); + Map<String, JClassType> mappings = new TreeMap<String, JClassType>(); // Keep track of what has happened to avoid logging intermediate state - Map<JClassType, List<JClassType>> replaced = new HashMap<JClassType, List<JClassType>>(); + Map<JClassType, List<JClassType>> replaced = new TreeMap<JClassType, List<JClassType>>( + ConnectorBundle.jClassComparator); for (JClassType type : types) { Connect connectAnnotation = type.getAnnotation(Connect.class); diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java index fedc86fbf6..b4531eb08e 100644 --- a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/ConnectorBundle.java @@ -19,12 +19,16 @@ import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; @@ -37,7 +41,6 @@ import com.google.gwt.core.ext.typeinfo.JParameterizedType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.core.ext.typeinfo.NotFoundException; import com.google.gwt.core.ext.typeinfo.TypeOracle; -import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ServerConnector; @@ -54,6 +57,22 @@ import elemental.json.JsonValue; public class ConnectorBundle { private static final String FAIL_IF_NOT_SERIALIZABLE = "vFailIfNotSerializable"; + public static final Comparator<JClassType> jClassComparator = new Comparator<JClassType>() { + @Override + public int compare(JClassType o1, JClassType o2) { + return o1.getQualifiedSourceName().compareTo( + o2.getQualifiedSourceName()); + } + }; + + public static final Comparator<JMethod> jMethodComparator = new Comparator<JMethod>() { + @Override + public int compare(JMethod o1, JMethod o2) { + return o1.getReadableDeclaration().compareTo( + o2.getReadableDeclaration()); + } + }; + private final String name; private final ConnectorBundle previousBundle; private final Collection<TypeVisitor> visitors; @@ -61,24 +80,42 @@ public class ConnectorBundle { private final Set<JType> hasSerializeSupport = new HashSet<JType>(); private final Set<JType> needsSerializeSupport = new HashSet<JType>(); - private final Map<JType, GeneratedSerializer> serializers = new HashMap<JType, GeneratedSerializer>(); - private final Map<JClassType, JType> presentationTypes = new HashMap<JClassType, JType>(); - private final Set<JClassType> needsSuperClass = new HashSet<JClassType>(); - private final Set<JClassType> needsGwtConstructor = new HashSet<JClassType>(); + private final Map<JType, GeneratedSerializer> serializers = new TreeMap<JType, GeneratedSerializer>( + new Comparator<JType>() { + @Override + public int compare(JType o1, JType o2) { + return o1.toString().compareTo(o2.toString()); + } + }); + + private final Map<JClassType, Map<JMethod, Set<MethodAttribute>>> methodAttributes = new TreeMap<JClassType, Map<JMethod, Set<MethodAttribute>>>( + jClassComparator); + private final Set<JClassType> needsSuperClass = new TreeSet<JClassType>( + jClassComparator); + private final Set<JClassType> needsGwtConstructor = new TreeSet<JClassType>( + jClassComparator); private final Set<JClassType> visitedTypes = new HashSet<JClassType>(); - private final Set<JClassType> needsProxySupport = new HashSet<JClassType>(); - private final Map<JClassType, Set<String>> identifiers = new HashMap<JClassType, Set<String>>(); - private final Map<JClassType, Set<JMethod>> needsReturnType = new HashMap<JClassType, Set<JMethod>>(); - private final Map<JClassType, Set<JMethod>> needsInvoker = new HashMap<JClassType, Set<JMethod>>(); - private final Map<JClassType, Set<JMethod>> needsParamTypes = new HashMap<JClassType, Set<JMethod>>(); - private final Map<JClassType, Set<JMethod>> needsOnStateChange = new HashMap<JClassType, Set<JMethod>>(); - - private final Map<JClassType, Map<JMethod, Set<MethodAttribute>>> methodAttributes = new HashMap<JClassType, Map<JMethod, Set<MethodAttribute>>>(); - - private final Set<Property> needsProperty = new HashSet<Property>(); - private final Map<JClassType, Set<Property>> needsDelegateToWidget = new HashMap<JClassType, Set<Property>>(); + private final Set<JClassType> needsProxySupport = new TreeSet<JClassType>( + jClassComparator); + + private final Map<JClassType, JType> presentationTypes = new TreeMap<JClassType, JType>( + jClassComparator); + private final Map<JClassType, Set<String>> identifiers = new TreeMap<JClassType, Set<String>>( + jClassComparator); + private final Map<JClassType, Set<JMethod>> needsReturnType = new TreeMap<JClassType, Set<JMethod>>( + jClassComparator); + private final Map<JClassType, Set<JMethod>> needsInvoker = new TreeMap<JClassType, Set<JMethod>>( + jClassComparator); + private final Map<JClassType, Set<JMethod>> needsParamTypes = new TreeMap<JClassType, Set<JMethod>>( + jClassComparator); + private final Map<JClassType, Set<JMethod>> needsOnStateChange = new TreeMap<JClassType, Set<JMethod>>( + jClassComparator); + + private final Set<Property> needsProperty = new TreeSet<Property>(); + private final Map<JClassType, Set<Property>> needsDelegateToWidget = new TreeMap<JClassType, Set<Property>>( + jClassComparator); private ConnectorBundle(String name, ConnectorBundle previousBundle, Collection<TypeVisitor> visitors, @@ -369,7 +406,7 @@ public class ConnectorBundle { } public Collection<Property> getProperties(JClassType type) { - HashSet<Property> properties = new HashSet<Property>(); + Set<Property> properties = new TreeSet<Property>(); properties.addAll(MethodProperty.findProperties(type)); properties.addAll(FieldProperty.findProperties(type)); @@ -463,10 +500,19 @@ public class ConnectorBundle { } } - private <K, V> void addMapping(Map<K, Set<V>> map, K key, V value) { - Set<V> set = map.get(key); + private <K> void addMapping(Map<K, Set<String>> map, K key, String value) { + Set<String> set = map.get(key); + if (set == null) { + set = new TreeSet<String>(); + map.put(key, set); + } + set.add(value); + } + + private <K> void addMapping(Map<K, Set<JMethod>> map, K key, JMethod value) { + Set<JMethod> set = map.get(key); if (set == null) { - set = new HashSet<V>(); + set = new TreeSet<JMethod>(jMethodComparator); map.put(key, set); } set.add(value); @@ -533,11 +579,26 @@ public class ConnectorBundle { Map<JMethod, Set<MethodAttribute>> typeData = methodAttributes .get(type); if (typeData == null) { - typeData = new HashMap<JMethod, Set<MethodAttribute>>(); + typeData = new TreeMap<JMethod, Set<MethodAttribute>>( + jMethodComparator); methodAttributes.put(type, typeData); } - addMapping(typeData, method, methodAttribute); + Map<JMethod, Set<MethodAttribute>> methods = methodAttributes + .get(type); + if (methods == null) { + methods = new TreeMap<JMethod, Set<MethodAttribute>>( + jMethodComparator); + methodAttributes.put(type, methods); + } + + Set<MethodAttribute> attributes = methods.get(method); + if (attributes == null) { + attributes = new TreeSet<MethodAttribute>(); + methods.put(method, attributes); + } + + attributes.add(methodAttribute); } } @@ -564,7 +625,7 @@ public class ConnectorBundle { } } - private static Set<Class<?>> frameworkHandledTypes = new HashSet<Class<?>>(); + private static Set<Class<?>> frameworkHandledTypes = new LinkedHashSet<Class<?>>(); { frameworkHandledTypes.add(String.class); frameworkHandledTypes.add(Boolean.class); @@ -611,7 +672,9 @@ public class ConnectorBundle { public void setNeedsDelegateToWidget(Property property, JClassType type) { if (!isNeedsDelegateToWidget(type)) { - needsDelegateToWidget.put(type, Sets.newHashSet(property)); + TreeSet<Property> set = new TreeSet<Property>(); + set.add(property); + needsDelegateToWidget.put(type, set); } else if (!needsDelegateToWidget.get(type).contains(property)) { needsDelegateToWidget.get(type).add(property); } diff --git a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java index f07b1c29fd..0c849bead5 100644 --- a/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java +++ b/client-compiler/src/com/vaadin/server/widgetsetutils/metadata/Property.java @@ -24,7 +24,7 @@ import com.google.gwt.core.ext.typeinfo.JPrimitiveType; import com.google.gwt.core.ext.typeinfo.JType; import com.google.gwt.user.rebind.SourceWriter; -public abstract class Property { +public abstract class Property implements Comparable<Property> { private final String name; private final JClassType beanType; private final JType propertyType; @@ -107,6 +107,20 @@ public abstract class Property { + getName().hashCode(); } + @Override + public int compareTo(Property o) { + int comp = getName().compareTo(o.getName()); + if (comp == 0) { + comp = getBeanType().getQualifiedSourceName().compareTo( + o.getBeanType().getQualifiedSourceName()); + } + if (comp == 0) { + comp = getClass().getCanonicalName().compareTo( + o.getClass().getCanonicalName()); + } + return comp; + } + public abstract <T extends Annotation> T getAnnotation( Class<T> annotationClass); diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index 70275218cf..6e20908274 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -62,7 +62,6 @@ import com.vaadin.client.ui.VContextMenu; import com.vaadin.client.ui.VNotification; import com.vaadin.client.ui.VOverlay; import com.vaadin.client.ui.ui.UIConnector; -import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.VaadinUriResolver; import com.vaadin.shared.Version; import com.vaadin.shared.communication.LegacyChangeVariablesInvocation; @@ -1318,20 +1317,19 @@ public class ApplicationConnection implements HasHandlers { * before the component is updated so the value is correct if called from * updatedFromUIDL. * - * @param paintable + * @param connector * The connector to register event listeners for * @param eventIdentifier * The identifier for the event * @return true if at least one listener has been registered on server side * for the event identified by eventIdentifier. * @deprecated As of 7.0. Use - * {@link AbstractComponentState#hasEventListener(String)} - * instead + * {@link AbstractConnector#hasEventListener(String)} instead */ @Deprecated - public boolean hasEventListeners(ComponentConnector paintable, + public boolean hasEventListeners(ComponentConnector connector, String eventIdentifier) { - return paintable.hasEventListener(eventIdentifier); + return connector.hasEventListener(eventIdentifier); } /** diff --git a/client/src/com/vaadin/client/BrowserInfo.java b/client/src/com/vaadin/client/BrowserInfo.java index 8b274623c1..8dcefddcf5 100644 --- a/client/src/com/vaadin/client/BrowserInfo.java +++ b/client/src/com/vaadin/client/BrowserInfo.java @@ -30,6 +30,7 @@ public class BrowserInfo { private static final String BROWSER_OPERA = "op"; private static final String BROWSER_IE = "ie"; + private static final String BROWSER_EDGE = "edge"; private static final String BROWSER_FIREFOX = "ff"; private static final String BROWSER_SAFARI = "sa"; @@ -171,6 +172,13 @@ public class BrowserInfo { minorVersionClass = majorVersionClass + browserDetails.getBrowserMinorVersion(); browserEngineClass = ENGINE_TRIDENT; + } else if (browserDetails.isEdge()) { + browserIdentifier = BROWSER_EDGE; + majorVersionClass = browserIdentifier + + getBrowserMajorVersion(); + minorVersionClass = majorVersionClass + + browserDetails.getBrowserMinorVersion(); + browserEngineClass = ""; } else if (browserDetails.isOpera()) { browserIdentifier = BROWSER_OPERA; majorVersionClass = browserIdentifier @@ -225,6 +233,10 @@ public class BrowserInfo { return browserDetails.isIE(); } + public boolean isEdge() { + return browserDetails.isEdge(); + } + public boolean isFirefox() { return browserDetails.isFirefox(); } @@ -245,6 +257,10 @@ public class BrowserInfo { return isIE() && getBrowserMajorVersion() == 10; } + public boolean isIE11() { + return isIE() && getBrowserMajorVersion() == 11; + } + public boolean isChrome() { return browserDetails.isChrome(); } diff --git a/client/src/com/vaadin/client/ComputedStyle.java b/client/src/com/vaadin/client/ComputedStyle.java index 61cb3c2eb6..66404eeeed 100644 --- a/client/src/com/vaadin/client/ComputedStyle.java +++ b/client/src/com/vaadin/client/ComputedStyle.java @@ -155,10 +155,10 @@ public class ComputedStyle { * the property to retrieve * @return the double value of the property */ - public final int getDoubleProperty(String name) { + public final double getDoubleProperty(String name) { Profiler.enter("ComputedStyle.getDoubleProperty"); String value = getProperty(name); - int result = parseDoubleNative(value); + double result = parseDoubleNative(value); Profiler.leave("ComputedStyle.getDoubleProperty"); return result; } @@ -275,9 +275,87 @@ public class ComputedStyle { * @return the value from the string before any non-numeric characters or * NaN if the value cannot be parsed as a number */ - private static native int parseDoubleNative(final String value) + private static native double parseDoubleNative(final String value) /*-{ return parseFloat(value); }-*/; + /** + * Returns the sum of the top and bottom border width + * + * @since 7.5.3 + * @return the sum of the top and bottom border + */ + public double getBorderHeight() { + double borderHeight = getDoubleProperty("borderTopWidth"); + borderHeight += getDoubleProperty("borderBottomWidth"); + + return borderHeight; + } + + /** + * Returns the sum of the left and right border width + * + * @since 7.5.3 + * @return the sum of the left and right border + */ + public double getBorderWidth() { + double borderWidth = getDoubleProperty("borderLeftWidth"); + borderWidth += getDoubleProperty("borderRightWidth"); + + return borderWidth; + } + + /** + * Returns the sum of the top and bottom padding + * + * @since 7.5.3 + * @return the sum of the top and bottom padding + */ + public double getPaddingHeight() { + double paddingHeight = getDoubleProperty("paddingTop"); + paddingHeight += getDoubleProperty("paddingBottom"); + + return paddingHeight; + } + + /** + * Returns the sum of the top and bottom padding + * + * @since 7.5.3 + * @return the sum of the left and right padding + */ + public double getPaddingWidth() { + double paddingWidth = getDoubleProperty("paddingLeft"); + paddingWidth += getDoubleProperty("paddingRight"); + + return paddingWidth; + } + + /** + * Returns the sum of the top and bottom margin + * + * @since 7.6 + * @return the sum of the top and bottom margin + */ + public double getMarginHeight() { + double marginHeight = getDoubleProperty("marginTop"); + marginHeight += getDoubleProperty("marginBottom"); + + return marginHeight; + } + + /** + * Returns the sum of the top and bottom margin + * + * @since 7.6 + * @return the sum of the left and right margin + */ + public double getMarginWidth() { + double marginWidth = getDoubleProperty("marginLeft"); + marginWidth += getDoubleProperty("marginRight"); + + return marginWidth; + } + } diff --git a/client/src/com/vaadin/client/EventHelper.java b/client/src/com/vaadin/client/EventHelper.java index f251215d41..1ee252af0f 100644 --- a/client/src/com/vaadin/client/EventHelper.java +++ b/client/src/com/vaadin/client/EventHelper.java @@ -51,7 +51,6 @@ import com.google.gwt.user.client.ui.Widget; * * * </pre> - * */ public class EventHelper { @@ -69,7 +68,7 @@ public class EventHelper { */ public static <T extends ComponentConnector & FocusHandler> HandlerRegistration updateFocusHandler( T connector, HandlerRegistration handlerRegistration) { - return updateHandler(connector, FOCUS, handlerRegistration, + return updateHandler(connector, connector, FOCUS, handlerRegistration, FocusEvent.getType(), connector.getWidget()); } @@ -89,7 +88,7 @@ public class EventHelper { */ public static <T extends ComponentConnector & FocusHandler> HandlerRegistration updateFocusHandler( T connector, HandlerRegistration handlerRegistration, Widget widget) { - return updateHandler(connector, FOCUS, handlerRegistration, + return updateHandler(connector, connector, FOCUS, handlerRegistration, FocusEvent.getType(), widget); } @@ -107,7 +106,7 @@ public class EventHelper { */ public static <T extends ComponentConnector & BlurHandler> HandlerRegistration updateBlurHandler( T connector, HandlerRegistration handlerRegistration) { - return updateHandler(connector, BLUR, handlerRegistration, + return updateHandler(connector, connector, BLUR, handlerRegistration, BlurEvent.getType(), connector.getWidget()); } @@ -128,23 +127,21 @@ public class EventHelper { */ public static <T extends ComponentConnector & BlurHandler> HandlerRegistration updateBlurHandler( T connector, HandlerRegistration handlerRegistration, Widget widget) { - return updateHandler(connector, BLUR, handlerRegistration, + return updateHandler(connector, connector, BLUR, handlerRegistration, BlurEvent.getType(), widget); } - private static <H extends EventHandler> HandlerRegistration updateHandler( - ComponentConnector connector, String eventIdentifier, + public static <H extends EventHandler> HandlerRegistration updateHandler( + ComponentConnector connector, H handler, String eventIdentifier, HandlerRegistration handlerRegistration, Type<H> type, Widget widget) { if (connector.hasEventListener(eventIdentifier)) { if (handlerRegistration == null) { - handlerRegistration = widget.addDomHandler((H) connector, type); + handlerRegistration = widget.addDomHandler(handler, type); } } else if (handlerRegistration != null) { handlerRegistration.removeHandler(); handlerRegistration = null; } return handlerRegistration; - } - } diff --git a/client/src/com/vaadin/client/ResourceLoader.java b/client/src/com/vaadin/client/ResourceLoader.java index 9e9ce5ac49..559768d09c 100644 --- a/client/src/com/vaadin/client/ResourceLoader.java +++ b/client/src/com/vaadin/client/ResourceLoader.java @@ -283,8 +283,7 @@ public class ResourceLoader { * @since 7.2.4 */ public static boolean supportsInOrderScriptExecution() { - return BrowserInfo.get().isIE() - && BrowserInfo.get().getBrowserMajorVersion() >= 11; + return BrowserInfo.get().isIE11() || BrowserInfo.get().isEdge(); } /** @@ -486,10 +485,11 @@ public class ResourceLoader { addOnloadHandler(linkElement, new ResourceLoadListener() { @Override public void onLoad(ResourceLoadEvent event) { - // Chrome && IE fires load for errors, must check + // Chrome, IE, Edge all fire load for errors, must check // stylesheet data if (BrowserInfo.get().isChrome() - || BrowserInfo.get().isIE()) { + || BrowserInfo.get().isIE() + || BrowserInfo.get().isEdge()) { int styleSheetLength = getStyleSheetLength(url); // Error if there's an empty stylesheet if (styleSheetLength == 0) { diff --git a/client/src/com/vaadin/client/SuperDevMode.java b/client/src/com/vaadin/client/SuperDevMode.java index c72cd73939..f664244715 100644 --- a/client/src/com/vaadin/client/SuperDevMode.java +++ b/client/src/com/vaadin/client/SuperDevMode.java @@ -189,7 +189,11 @@ public class SuperDevMode { if (serverUrl == null || "".equals(serverUrl)) { serverUrl = "http://localhost:9876/"; } else { - serverUrl = "http://" + serverUrl + "/"; + if (serverUrl.contains(":")) { + serverUrl = "http://" + serverUrl + "/"; + } else { + serverUrl = "http://" + serverUrl + ":9876/"; + } } if (hasSession(SKIP_RECOMPILE)) { diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java index 4e59040298..f3d65cd20a 100644 --- a/client/src/com/vaadin/client/VTooltip.java +++ b/client/src/com/vaadin/client/VTooltip.java @@ -89,6 +89,9 @@ public class VTooltip extends VOverlay { LiveValue.ASSERTIVE); Roles.getTooltipRole().setAriaRelevantProperty(getElement(), RelevantValue.ADDITIONS); + + // Tooltip needs to be on top of other VOverlay elements. + setZIndex(VOverlay.Z_INDEX + 1); } /** diff --git a/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java new file mode 100644 index 0000000000..8ca2292bc5 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/AbstractSelectionModelConnector.java @@ -0,0 +1,82 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import java.util.Collection; + +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.extensions.AbstractExtensionConnector; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.ui.grid.GridState; + +import elemental.json.JsonObject; + +/** + * Base class for all selection model connectors. + * + * @since + * @author Vaadin Ltd + */ +public abstract class AbstractSelectionModelConnector<T extends SelectionModel<JsonObject>> + extends AbstractExtensionConnector { + + @Override + public GridConnector getParent() { + return (GridConnector) super.getParent(); + } + + protected Grid<JsonObject> getGrid() { + return getParent().getWidget(); + } + + protected RowHandle<JsonObject> getRowHandle(JsonObject row) { + return getGrid().getDataSource().getHandle(row); + } + + protected String getRowKey(JsonObject row) { + return row != null ? getParent().getRowKey(row) : null; + } + + protected abstract T createSelectionModel(); + + public abstract static class AbstractSelectionModel implements + SelectionModel<JsonObject> { + + @Override + public boolean isSelected(JsonObject row) { + return row.hasKey(GridState.JSONKEY_SELECTED); + } + + @Override + public void setGrid(Grid<JsonObject> grid) { + // NO-OP + } + + @Override + public void reset() { + // Should not need any actions. + } + + @Override + public Collection<JsonObject> getSelectedRows() { + throw new UnsupportedOperationException( + "This client-side selection model " + + getClass().getSimpleName() + + " does not know selected rows."); + } + } +} diff --git a/client/src/com/vaadin/client/connectors/GridConnector.java b/client/src/com/vaadin/client/connectors/GridConnector.java index ef52a429e7..1070a46287 100644 --- a/client/src/com/vaadin/client/connectors/GridConnector.java +++ b/client/src/com/vaadin/client/connectors/GridConnector.java @@ -19,33 +19,36 @@ package com.vaadin.client.connectors; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.DeferredWorker; import com.vaadin.client.MouseEventDetailsBuilder; -import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.TooltipInfo; import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.connectors.RpcDataSourceConnector.DetailsListener; import com.vaadin.client.connectors.RpcDataSourceConnector.RpcDataSource; -import com.vaadin.client.data.DataSource.RowHandle; -import com.vaadin.client.renderers.Renderer; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.AbstractHasComponentsConnector; +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.widget.grid.CellReference; import com.vaadin.client.widget.grid.CellStyleGenerator; @@ -59,17 +62,12 @@ import com.vaadin.client.widget.grid.events.ColumnReorderEvent; import com.vaadin.client.widget.grid.events.ColumnReorderHandler; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; +import com.vaadin.client.widget.grid.events.EditorCloseEvent; +import com.vaadin.client.widget.grid.events.EditorEventHandler; +import com.vaadin.client.widget.grid.events.EditorMoveEvent; +import com.vaadin.client.widget.grid.events.EditorOpenEvent; import com.vaadin.client.widget.grid.events.GridClickEvent; import com.vaadin.client.widget.grid.events.GridDoubleClickEvent; -import com.vaadin.client.widget.grid.events.SelectAllEvent; -import com.vaadin.client.widget.grid.events.SelectAllHandler; -import com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel; -import com.vaadin.client.widget.grid.selection.SelectionEvent; -import com.vaadin.client.widget.grid.selection.SelectionHandler; -import com.vaadin.client.widget.grid.selection.SelectionModel; -import com.vaadin.client.widget.grid.selection.SelectionModelMulti; -import com.vaadin.client.widget.grid.selection.SelectionModelNone; -import com.vaadin.client.widget.grid.selection.SelectionModelSingle; import com.vaadin.client.widget.grid.sort.SortEvent; import com.vaadin.client.widget.grid.sort.SortHandler; import com.vaadin.client.widget.grid.sort.SortOrder; @@ -79,10 +77,8 @@ import com.vaadin.client.widgets.Grid.FooterCell; import com.vaadin.client.widgets.Grid.FooterRow; import com.vaadin.client.widgets.Grid.HeaderCell; import com.vaadin.client.widgets.Grid.HeaderRow; -import com.vaadin.shared.Connector; import com.vaadin.shared.data.sort.SortDirection; import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.grid.DetailsConnectorChange; import com.vaadin.shared.ui.grid.EditorClientRpc; import com.vaadin.shared.ui.grid.EditorServerRpc; import com.vaadin.shared.ui.grid.GridClientRpc; @@ -90,7 +86,6 @@ import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridConstants; import com.vaadin.shared.ui.grid.GridServerRpc; import com.vaadin.shared.ui.grid.GridState; -import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode; import com.vaadin.shared.ui.grid.GridStaticSectionState; import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState; import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState; @@ -114,8 +109,8 @@ import elemental.json.JsonValue; public class GridConnector extends AbstractHasComponentsConnector implements SimpleManagedLayout, DeferredWorker { - private static final class CustomCellStyleGenerator implements - CellStyleGenerator<JsonObject> { + private static final class CustomStyleGenerator implements + CellStyleGenerator<JsonObject>, RowStyleGenerator<JsonObject> { @Override public String getStyle(CellReference<JsonObject> cellReference) { JsonObject row = cellReference.getRow(); @@ -141,10 +136,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - } - - private static final class CustomRowStyleGenerator implements - RowStyleGenerator<JsonObject> { @Override public String getStyle(RowReference<JsonObject> rowReference) { JsonObject row = rowReference.getRow(); @@ -154,7 +145,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements return null; } } - } /** @@ -169,6 +159,8 @@ public class GridConnector extends AbstractHasComponentsConnector implements private AbstractFieldConnector editorConnector; + private HandlerRegistration errorStateHandler; + public CustomGridColumn(String id, AbstractRendererConnector<Object> rendererConnector) { super(rendererConnector.getRenderer()); @@ -205,8 +197,54 @@ public class GridConnector extends AbstractHasComponentsConnector implements return editorConnector; } - private void setEditorConnector(AbstractFieldConnector editorConnector) { + private void setEditorConnector( + final AbstractFieldConnector editorConnector) { this.editorConnector = editorConnector; + + if (errorStateHandler != null) { + errorStateHandler.removeHandler(); + errorStateHandler = null; + } + + // Avoid nesting too deep + if (editorConnector == null) { + return; + } + + errorStateHandler = editorConnector.addStateChangeHandler( + "errorMessage", new StateChangeHandler() { + + @Override + public void onStateChanged( + StateChangeEvent stateChangeEvent) { + + String error = editorConnector.getState().errorMessage; + + if (error == null) { + columnToErrorMessage + .remove(CustomGridColumn.this); + } else { + // The error message is formatted as HTML; + // therefore, we use this hack to make the + // string human-readable. + Element e = DOM.createElement("div"); + e.setInnerHTML(editorConnector.getState().errorMessage); + error = getHeaderCaption() + ": " + + e.getInnerText(); + + columnToErrorMessage.put(CustomGridColumn.this, + error); + } + + // Editor should not be touched while there's a + // request pending. + if (editorHandler.currentRequest == null) { + getWidget().getEditor().setEditorError( + getColumnErrors(), + columnToErrorMessage.keySet()); + } + } + }); } } @@ -281,7 +319,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements if (column instanceof CustomGridColumn) { AbstractFieldConnector c = ((CustomGridColumn) column) .getEditorConnector(); - return c != null ? c.getWidget() : null; + + if (c == null) { + return null; + } + + return c.getWidget(); } else { throw new IllegalStateException("Unexpected column type: " + column.getClass().getName()); @@ -420,255 +463,105 @@ public class GridConnector extends AbstractHasComponentsConnector implements } }; - private static class CustomDetailsGenerator implements DetailsGenerator { + private class CustomDetailsGenerator implements DetailsGenerator { - private final Map<Integer, ComponentConnector> indexToDetailsMap = new HashMap<Integer, ComponentConnector>(); + private final Map<String, ComponentConnector> idToDetailsMap = new HashMap<String, ComponentConnector>(); + private final Map<String, Integer> idToRowIndex = new HashMap<String, Integer>(); @Override - @SuppressWarnings("boxing") public Widget getDetails(int rowIndex) { - ComponentConnector componentConnector = indexToDetailsMap - .get(rowIndex); - if (componentConnector != null) { - return componentConnector.getWidget(); - } else { - return null; - } - } - - public void setDetailsConnectorChanges( - Set<DetailsConnectorChange> changes) { - /* - * To avoid overwriting connectors while moving them about, we'll - * take all the affected connectors, first all remove those that are - * removed or moved, then we add back those that are moved or added. - */ + JsonObject row = getWidget().getDataSource().getRow(rowIndex); - /* Remove moved/removed connectors from bookkeeping */ - for (DetailsConnectorChange change : changes) { - Integer oldIndex = change.getOldIndex(); - Connector removedConnector = indexToDetailsMap.remove(oldIndex); - - Connector connector = change.getConnector(); - assert removedConnector == null || connector == null - || removedConnector.equals(connector) : "Index " - + oldIndex + " points to " + removedConnector - + " while " + connector + " was expected"; + if (!row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) + || row.getString(GridState.JSONKEY_DETAILS_VISIBLE) + .isEmpty()) { + return null; } - /* Add moved/added connectors to bookkeeping */ - for (DetailsConnectorChange change : changes) { - Integer newIndex = change.getNewIndex(); - ComponentConnector connector = (ComponentConnector) change - .getConnector(); - - if (connector != null) { - assert newIndex != null : "An existing connector has a missing new index."; - - ComponentConnector prevConnector = indexToDetailsMap.put( - newIndex, connector); + String id = row.getString(GridState.JSONKEY_DETAILS_VISIBLE); + ComponentConnector componentConnector = idToDetailsMap.get(id); + idToRowIndex.put(id, rowIndex); - assert prevConnector == null : "Connector collision at index " - + newIndex - + " between old " - + prevConnector - + " and new " + connector; - } - } + return componentConnector.getWidget(); } - } - - @SuppressWarnings("boxing") - private static class DetailsConnectorFetcher implements DeferredWorker { - private static final int FETCH_TIMEOUT_MS = 5000; - - public interface Listener { - void fetchHasBeenScheduled(int id); - - void fetchHasReturned(int id); - } - - /** A flag making sure that we don't call scheduleFinally many times. */ - private boolean fetcherHasBeenCalled = false; - - /** A rolling counter for unique values. */ - private int detailsFetchCounter = 0; - - /** A collection that tracks the amount of requests currently underway. */ - private Set<Integer> pendingFetches = new HashSet<Integer>(5); - - private final ScheduledCommand lazyDetailsFetcher = new ScheduledCommand() { - @Override - public void execute() { - int currentFetchId = detailsFetchCounter++; - pendingFetches.add(currentFetchId); - rpc.sendDetailsComponents(currentFetchId); - fetcherHasBeenCalled = false; - - if (listener != null) { - listener.fetchHasBeenScheduled(currentFetchId); + public void updateConnectorHierarchy(List<ServerConnector> children) { + Set<String> connectorIds = new HashSet<String>(); + for (ServerConnector child : children) { + if (child instanceof ComponentConnector) { + connectorIds.add(child.getConnectorId()); + idToDetailsMap.put(child.getConnectorId(), + (ComponentConnector) child); } - - assert assertRequestDoesNotTimeout(currentFetchId); } - }; - - private DetailsConnectorFetcher.Listener listener = null; - - private final GridServerRpc rpc; - public DetailsConnectorFetcher(GridServerRpc rpc) { - assert rpc != null : "RPC was null"; - this.rpc = rpc; - } - - public void schedule() { - if (!fetcherHasBeenCalled) { - Scheduler.get().scheduleFinally(lazyDetailsFetcher); - fetcherHasBeenCalled = true; - } - } - - public void responseReceived(int fetchId) { - - if (fetchId < 0) { - /* Ignore negative fetchIds (they're pushed, not fetched) */ - return; + Set<String> removedDetails = new HashSet<String>(); + for (Entry<String, ComponentConnector> entry : idToDetailsMap + .entrySet()) { + ComponentConnector connector = entry.getValue(); + String id = connector.getConnectorId(); + if (!connectorIds.contains(id)) { + removedDetails.add(entry.getKey()); + if (idToRowIndex.containsKey(id)) { + getWidget().setDetailsVisible(idToRowIndex.get(id), + false); + } + } } - boolean success = pendingFetches.remove(fetchId); - assert success : "Received a response with an unidentified fetch id"; - - if (listener != null) { - listener.fetchHasReturned(fetchId); + for (String id : removedDetails) { + idToDetailsMap.remove(id); + idToRowIndex.remove(id); } } - - @Override - public boolean isWorkPending() { - return fetcherHasBeenCalled || !pendingFetches.isEmpty(); - } - - private boolean assertRequestDoesNotTimeout(final int fetchId) { - /* - * This method will not be compiled without asserts enabled. This - * only makes sure that any request does not time out. - * - * TODO Should this be an explicit check? Is it worth the overhead? - */ - new Timer() { - @Override - public void run() { - assert !pendingFetches.contains(fetchId) : "Fetch id " - + fetchId + " timed out."; - } - }.schedule(FETCH_TIMEOUT_MS); - return true; - } - - public void setListener(DetailsConnectorFetcher.Listener listener) { - // if more are needed, feel free to convert this into a collection. - this.listener = listener; - } } /** - * The functionality that makes sure that the scroll position is still kept - * up-to-date even if more details are being fetched lazily. + * Class for handling scrolling issues with open details. + * + * @since 7.5.2 */ - private class LazyDetailsScrollAdjuster implements DeferredWorker { - - private static final int SCROLL_TO_END_ID = -2; - private static final int NO_SCROLL_SCHEDULED = -1; - - private class ScrollStopChecker implements DeferredWorker { - private final ScheduledCommand checkCommand = new ScheduledCommand() { - @Override - public void execute() { - isScheduled = false; - if (queuedFetches.isEmpty()) { - currentRow = NO_SCROLL_SCHEDULED; - destination = null; - } - } - }; - - private boolean isScheduled = false; - - public void schedule() { - if (isScheduled) { - return; - } - Scheduler.get().scheduleDeferred(checkCommand); - isScheduled = true; - } + private class LazyDetailsScroller implements DeferredWorker { - @Override - public boolean isWorkPending() { - return isScheduled; - } - } + /* Timer value tested to work in our test cluster with slow IE8s. */ + private static final int DISABLE_LAZY_SCROLL_TIMEOUT = 1500; - private DetailsConnectorFetcher.Listener fetcherListener = new DetailsConnectorFetcher.Listener() { + /* + * Cancels details opening scroll after timeout. Avoids any unexpected + * scrolls via details opening. + */ + private Timer disableScroller = new Timer() { @Override - @SuppressWarnings("boxing") - public void fetchHasBeenScheduled(int id) { - if (currentRow != NO_SCROLL_SCHEDULED) { - queuedFetches.add(id); - } - } - - @Override - @SuppressWarnings("boxing") - public void fetchHasReturned(int id) { - if (currentRow == NO_SCROLL_SCHEDULED - || queuedFetches.isEmpty()) { - return; - } - - queuedFetches.remove(id); - if (currentRow == SCROLL_TO_END_ID) { - getWidget().scrollToEnd(); - } else { - getWidget().scrollToRow(currentRow, destination); - } - - /* - * Schedule a deferred call whether we should stop adjusting for - * scrolling. - * - * This is done deferredly just because we can't be absolutely - * certain whether this most recent scrolling won't cascade into - * further lazy details loading (perhaps deferredly). - */ - scrollStopChecker.schedule(); + public void run() { + targetRow = -1; } }; - private int currentRow = NO_SCROLL_SCHEDULED; - private final Set<Integer> queuedFetches = new HashSet<Integer>(); - private final ScrollStopChecker scrollStopChecker = new ScrollStopChecker(); - private ScrollDestination destination; - - public LazyDetailsScrollAdjuster() { - detailsConnectorFetcher.setListener(fetcherListener); - } + private Integer targetRow = -1; + private ScrollDestination destination = null; - public void adjustForEnd() { - currentRow = SCROLL_TO_END_ID; + public void scrollToRow(Integer row, ScrollDestination dest) { + targetRow = row; + destination = dest; + disableScroller.schedule(DISABLE_LAZY_SCROLL_TIMEOUT); } - public void adjustFor(int row, ScrollDestination destination) { - currentRow = row; - this.destination = destination; + /** + * Inform LazyDetailsScroller that a details row has opened on a row. + * + * @param rowIndex + * index of row with details now open + */ + public void detailsOpened(int rowIndex) { + if (targetRow == rowIndex) { + getWidget().scrollToRow(targetRow, destination); + disableScroller.run(); + } } @Override public boolean isWorkPending() { - return currentRow != NO_SCROLL_SCHEDULED - || !queuedFetches.isEmpty() - || scrollStopChecker.isWorkPending(); + return disableScroller.isRunning(); } } @@ -677,20 +570,9 @@ public class GridConnector extends AbstractHasComponentsConnector implements */ private Map<String, CustomGridColumn> columnIdToColumn = new HashMap<String, CustomGridColumn>(); - private AbstractRowHandleSelectionModel<JsonObject> selectionModel; - private Set<String> selectedKeys = new LinkedHashSet<String>(); private List<String> columnOrder = new ArrayList<String>(); /** - * {@link #selectionUpdatedFromState} is set to true when - * {@link #updateSelectionFromState()} makes changes to selection. This flag - * tells the {@code internalSelectionChangeHandler} to not send same data - * straight back to server. Said listener sets it back to false when - * handling that event. - */ - private boolean selectionUpdatedFromState; - - /** * {@link #columnsUpdatedFromState} is set to true when * {@link #updateColumnOrderFromState(List)} is updating the column order * for the widget. This flag tells the {@link #columnReorderHandler} to not @@ -701,68 +583,57 @@ public class GridConnector extends AbstractHasComponentsConnector implements private RpcDataSource dataSource; - private SelectionHandler<JsonObject> internalSelectionChangeHandler = new SelectionHandler<JsonObject>() { - @Override - public void onSelect(SelectionEvent<JsonObject> event) { - if (event.isBatchedSelection()) { - return; - } - if (!selectionUpdatedFromState) { - for (JsonObject row : event.getRemoved()) { - selectedKeys.remove(dataSource.getRowKey(row)); - } - - for (JsonObject row : event.getAdded()) { - selectedKeys.add(dataSource.getRowKey(row)); - } - - getRpcProxy(GridServerRpc.class).select( - new ArrayList<String>(selectedKeys)); - } else { - selectionUpdatedFromState = false; - } - } - }; + /* Used to track Grid editor columns with validation errors */ + private final Map<Column<?, JsonObject>, String> columnToErrorMessage = new HashMap<Column<?, JsonObject>, String>(); private ItemClickHandler itemClickHandler = new ItemClickHandler(); private String lastKnownTheme = null; private final CustomDetailsGenerator customDetailsGenerator = new CustomDetailsGenerator(); - - private final DetailsConnectorFetcher detailsConnectorFetcher = new DetailsConnectorFetcher( - getRpcProxy(GridServerRpc.class)); + private final CustomStyleGenerator styleGenerator = new CustomStyleGenerator(); private final DetailsListener detailsListener = new DetailsListener() { @Override public void reapplyDetailsVisibility(final int rowIndex, final JsonObject row) { - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - if (hasDetailsOpen(row)) { - getWidget().setDetailsVisible(rowIndex, true); - detailsConnectorFetcher.schedule(); - } else { + if (hasDetailsOpen(row)) { + // Command for opening details row. + ScheduledCommand openDetails = new ScheduledCommand() { + @Override + public void execute() { + // Re-apply to force redraw. getWidget().setDetailsVisible(rowIndex, false); + getWidget().setDetailsVisible(rowIndex, true); + lazyDetailsScroller.detailsOpened(rowIndex); } + }; + + if (initialChange) { + Scheduler.get().scheduleDeferred(openDetails); + } else { + Scheduler.get().scheduleFinally(openDetails); } - }); + } else { + getWidget().setDetailsVisible(rowIndex, false); + } } private boolean hasDetailsOpen(JsonObject row) { return row.hasKey(GridState.JSONKEY_DETAILS_VISIBLE) - && row.getBoolean(GridState.JSONKEY_DETAILS_VISIBLE); - } - - @Override - public void closeDetails(int rowIndex) { - getWidget().setDetailsVisible(rowIndex, false); + && row.getString(GridState.JSONKEY_DETAILS_VISIBLE) != null; } }; - private final LazyDetailsScrollAdjuster lazyDetailsScrollAdjuster = new LazyDetailsScrollAdjuster(); + private final LazyDetailsScroller lazyDetailsScroller = new LazyDetailsScroller(); + private final CustomEditorHandler editorHandler = new CustomEditorHandler(); + + /* + * Initially details need to behave a bit differently to allow some + * escalator magic. + */ + private boolean initialChange; @Override @SuppressWarnings("unchecked") @@ -797,11 +668,13 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void scrollToEnd() { - lazyDetailsScrollAdjuster.adjustForEnd(); Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { getWidget().scrollToEnd(); + // Scrolls further if details opens. + lazyDetailsScroller.scrollToRow(dataSource.size() - 1, + ScrollDestination.END); } }); } @@ -809,11 +682,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void scrollToRow(final int row, final ScrollDestination destination) { - lazyDetailsScrollAdjuster.adjustFor(row, destination); Scheduler.get().scheduleFinally(new ScheduledCommand() { @Override public void execute() { getWidget().scrollToRow(row, destination); + // Scrolls a bit further if details opens. + lazyDetailsScroller.scrollToRow(row, destination); } }); } @@ -822,59 +696,16 @@ public class GridConnector extends AbstractHasComponentsConnector implements public void recalculateColumnWidths() { getWidget().recalculateColumnWidths(); } - - @Override - @SuppressWarnings("boxing") - public void setDetailsConnectorChanges( - Set<DetailsConnectorChange> connectorChanges, int fetchId) { - customDetailsGenerator - .setDetailsConnectorChanges(connectorChanges); - - List<DetailsConnectorChange> removedFirst = new ArrayList<DetailsConnectorChange>( - connectorChanges); - Collections.sort(removedFirst, - DetailsConnectorChange.REMOVED_FIRST_COMPARATOR); - - // refresh moved/added details rows - for (DetailsConnectorChange change : removedFirst) { - Integer oldIndex = change.getOldIndex(); - Integer newIndex = change.getNewIndex(); - - assert oldIndex == null || oldIndex >= 0 : "Got an " - + "invalid old index: " + oldIndex - + " (connector: " + change.getConnector() + ")"; - assert newIndex == null || newIndex >= 0 : "Got an " - + "invalid new index: " + newIndex - + " (connector: " + change.getConnector() + ")"; - - if (oldIndex != null) { - /* Close the old/removed index */ - getWidget().setDetailsVisible(oldIndex, false); - - if (change.isShouldStillBeVisible()) { - getWidget().setDetailsVisible(oldIndex, true); - } - } - - if (newIndex != null) { - /* - * Since the component was lazy loaded, we need to - * refresh the details by toggling it. - */ - getWidget().setDetailsVisible(newIndex, false); - getWidget().setDetailsVisible(newIndex, true); - } - } - detailsConnectorFetcher.responseReceived(fetchId); - } }); - getWidget().addSelectionHandler(internalSelectionChangeHandler); - /* Item click events */ getWidget().addBodyClickHandler(itemClickHandler); getWidget().addBodyDoubleClickHandler(itemClickHandler); + /* Style Generators */ + getWidget().setCellStyleGenerator(styleGenerator); + getWidget().setRowStyleGenerator(styleGenerator); + getWidget().addSortHandler(new SortHandler<JsonObject>() { @Override public void sort(SortEvent<JsonObject> event) { @@ -899,36 +730,51 @@ public class GridConnector extends AbstractHasComponentsConnector implements } }); - getWidget().addSelectAllHandler(new SelectAllHandler<JsonObject>() { - - @Override - public void onSelectAll(SelectAllEvent<JsonObject> event) { - getRpcProxy(GridServerRpc.class).selectAll(); - } - - }); - - getWidget().setEditorHandler(new CustomEditorHandler()); + getWidget().setEditorHandler(editorHandler); getWidget().addColumnReorderHandler(columnReorderHandler); getWidget().addColumnVisibilityChangeHandler( columnVisibilityChangeHandler); + + ConnectorFocusAndBlurHandler.addHandlers(this); + getWidget().setDetailsGenerator(customDetailsGenerator); getLayoutManager().registerDependency(this, getWidget().getElement()); - layout(); - } + getWidget().addEditorEventHandler(new EditorEventHandler() { + @Override + public void onEditorOpen(EditorOpenEvent e) { + if (hasEventListener(GridConstants.EDITOR_OPEN_EVENT_ID)) { + String rowKey = getRowKey((JsonObject) e.getRow()); + getRpcProxy(GridServerRpc.class).editorOpen(rowKey); + } + } - @Override - public void onUnregister() { - customDetailsGenerator.indexToDetailsMap.clear(); + @Override + public void onEditorMove(EditorMoveEvent e) { + if (hasEventListener(GridConstants.EDITOR_MOVE_EVENT_ID)) { + String rowKey = getRowKey((JsonObject) e.getRow()); + getRpcProxy(GridServerRpc.class).editorMove(rowKey); + } + } - super.onUnregister(); + @Override + public void onEditorClose(EditorCloseEvent e) { + if (hasEventListener(GridConstants.EDITOR_CLOSE_EVENT_ID)) { + String rowKey = getRowKey((JsonObject) e.getRow()); + getRpcProxy(GridServerRpc.class).editorClose(rowKey); + } + } + }); + + layout(); } @Override public void onStateChanged(final StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); + initialChange = stateChangeEvent.isInitialStateChange(); + // Column updates if (stateChangeEvent.hasPropertyChanged("columns")) { @@ -959,19 +805,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements updateFooterFromState(getState().footer); } - // Selection - if (stateChangeEvent.hasPropertyChanged("selectionMode")) { - onSelectionModeChange(); - updateSelectDeselectAllowed(); - } else if (stateChangeEvent - .hasPropertyChanged("singleSelectDeselectAllowed")) { - updateSelectDeselectAllowed(); - } - - if (stateChangeEvent.hasPropertyChanged("selectedKeys")) { - updateSelectionFromState(); - } - // Sorting if (stateChangeEvent.hasPropertyChanged("sortColumns") || stateChangeEvent.hasPropertyChanged("sortDirs")) { @@ -998,14 +831,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } } - private void updateSelectDeselectAllowed() { - SelectionModel<JsonObject> model = getWidget().getSelectionModel(); - if (model instanceof SelectionModel.Single<?>) { - ((SelectionModel.Single<?>) model) - .setDeselectAllowed(getState().singleSelectDeselectAllowed); - } - } - private void updateColumnOrderFromState(List<String> stateColumnOrder) { CustomGridColumn[] columns = new CustomGridColumn[stateColumnOrder .size()]; @@ -1178,20 +1003,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements } /** - * If we have a selection column renderer, we need to offset the index by - * one when referring to the column index in the widget. - */ - private int getWidgetColumnIndex(final int columnIndex) { - Renderer<Boolean> selectionColumnRenderer = getWidget() - .getSelectionModel().getSelectionColumnRenderer(); - int widgetColumnIndex = columnIndex; - if (selectionColumnRenderer != null) { - widgetColumnIndex++; - } - return widgetColumnIndex; - } - - /** * Updates the column values from a state * * @param column @@ -1253,81 +1064,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements getWidget().setDataSource(this.dataSource); } - private void onSelectionModeChange() { - SharedSelectionMode mode = getState().selectionMode; - if (mode == null) { - getLogger().fine("ignored mode change"); - return; - } - - AbstractRowHandleSelectionModel<JsonObject> model = createSelectionModel(mode); - if (selectionModel == null - || !model.getClass().equals(selectionModel.getClass())) { - selectionModel = model; - getWidget().setSelectionModel(model); - selectedKeys.clear(); - } - } - - @OnStateChange("hasCellStyleGenerator") - private void onCellStyleGeneratorChange() { - if (getState().hasCellStyleGenerator) { - getWidget().setCellStyleGenerator(new CustomCellStyleGenerator()); - } else { - getWidget().setCellStyleGenerator(null); - } - } - - @OnStateChange("hasRowStyleGenerator") - private void onRowStyleGeneratorChange() { - if (getState().hasRowStyleGenerator) { - getWidget().setRowStyleGenerator(new CustomRowStyleGenerator()); - } else { - getWidget().setRowStyleGenerator(null); - } - } - - private void updateSelectionFromState() { - boolean changed = false; - - List<String> stateKeys = getState().selectedKeys; - - // find new deselections - for (String key : selectedKeys) { - if (!stateKeys.contains(key)) { - changed = true; - deselectByHandle(dataSource.getHandleByKey(key)); - } - } - - // find new selections - for (String key : stateKeys) { - if (!selectedKeys.contains(key)) { - changed = true; - selectByHandle(dataSource.getHandleByKey(key)); - } - } - - /* - * A defensive copy in case the collection in the state is mutated - * instead of re-assigned. - */ - selectedKeys = new LinkedHashSet<String>(stateKeys); - - /* - * We need to fire this event so that Grid is able to re-render the - * selection changes (if applicable). - */ - if (changed) { - // At least for now there's no way to send the selected and/or - // deselected row data. Some data is only stored as keys - selectionUpdatedFromState = true; - getWidget().fireEvent( - new SelectionEvent<JsonObject>(getWidget(), - (List<JsonObject>) null, null, false)); - } - } - private void onSortStateChange() { List<SortOrder> sortOrder = new ArrayList<SortOrder>(); @@ -1346,41 +1082,6 @@ public class GridConnector extends AbstractHasComponentsConnector implements return Logger.getLogger(getClass().getName()); } - @SuppressWarnings("static-method") - private AbstractRowHandleSelectionModel<JsonObject> createSelectionModel( - SharedSelectionMode mode) { - switch (mode) { - case SINGLE: - return new SelectionModelSingle<JsonObject>(); - case MULTI: - return new SelectionModelMulti<JsonObject>(); - case NONE: - return new SelectionModelNone<JsonObject>(); - default: - throw new IllegalStateException("unexpected mode value: " + mode); - } - } - - /** - * A workaround method for accessing the protected method - * {@code AbstractRowHandleSelectionModel.selectByHandle} - */ - private native void selectByHandle(RowHandle<JsonObject> handle) - /*-{ - var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel; - model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::selectByHandle(*)(handle); - }-*/; - - /** - * A workaround method for accessing the protected method - * {@code AbstractRowHandleSelectionModel.deselectByHandle} - */ - private native void deselectByHandle(RowHandle<JsonObject> handle) - /*-{ - var model = this.@com.vaadin.client.connectors.GridConnector::selectionModel; - model.@com.vaadin.client.widget.grid.selection.AbstractRowHandleSelectionModel::deselectByHandle(*)(handle); - }-*/; - /** * Gets the row key for a row object. * @@ -1405,12 +1106,12 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public void updateCaption(ComponentConnector connector) { // TODO Auto-generated method stub - } @Override public void onConnectorHierarchyChange( ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { + customDetailsGenerator.updateConnectorHierarchy(getChildren()); } public String getColumnId(Grid.Column<?, ?> column) { @@ -1427,8 +1128,7 @@ public class GridConnector extends AbstractHasComponentsConnector implements @Override public boolean isWorkPending() { - return detailsConnectorFetcher.isWorkPending() - || lazyDetailsScrollAdjuster.isWorkPending(); + return lazyDetailsScroller.isWorkPending(); } /** @@ -1441,4 +1141,74 @@ public class GridConnector extends AbstractHasComponentsConnector implements public DetailsListener getDetailsListener() { return detailsListener; } + + @Override + public boolean hasTooltip() { + return getState().hasDescriptions || super.hasTooltip(); + } + + @Override + public TooltipInfo getTooltipInfo(Element element) { + CellReference<JsonObject> cell = getWidget().getCellReference(element); + + if (cell != null) { + JsonObject row = cell.getRow(); + if (row == null) { + return null; + } + + Column<?, JsonObject> column = cell.getColumn(); + if (!(column instanceof CustomGridColumn)) { + // Selection checkbox column + return null; + } + CustomGridColumn c = (CustomGridColumn) column; + + JsonObject cellDescriptions = row + .getObject(GridState.JSONKEY_CELLDESCRIPTION); + + if (cellDescriptions != null && cellDescriptions.hasKey(c.id)) { + return new TooltipInfo(cellDescriptions.getString(c.id)); + } else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) { + return new TooltipInfo( + row.getString(GridState.JSONKEY_ROWDESCRIPTION)); + } else { + return null; + } + } + + return super.getTooltipInfo(element); + } + + /** + * Creates a concatenation of all columns errors for Editor. + * + * @since + * @return displayed error string + */ + private String getColumnErrors() { + List<String> errors = new ArrayList<String>(); + + for (Grid.Column<?, JsonObject> c : getWidget().getColumns()) { + if (!(c instanceof CustomGridColumn)) { + continue; + } + + String error = columnToErrorMessage.get(c); + if (error != null) { + errors.add(error); + } + } + + String result = ""; + Iterator<String> i = errors.iterator(); + while (i.hasNext()) { + result += i.next(); + if (i.hasNext()) { + result += ", "; + } + } + return result.isEmpty() ? null : result; + } + } diff --git a/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java new file mode 100644 index 0000000000..e4ad50e7ac --- /dev/null +++ b/client/src/com/vaadin/client/connectors/MultiSelectionModelConnector.java @@ -0,0 +1,383 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.CheckBox; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.data.DataSource; +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.ComplexRenderer; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.DataAvailableEvent; +import com.vaadin.client.widget.grid.DataAvailableHandler; +import com.vaadin.client.widget.grid.events.SelectAllEvent; +import com.vaadin.client.widget.grid.events.SelectAllHandler; +import com.vaadin.client.widget.grid.selection.MultiSelectionRenderer; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; +import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.HeaderCell; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.GridState; +import com.vaadin.shared.ui.grid.Range; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; +import com.vaadin.ui.Grid.MultiSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link MultiSelectionModel}. + * + * @since + * @author Vaadin Ltd + */ +@Connect(MultiSelectionModel.class) +public class MultiSelectionModelConnector extends + AbstractSelectionModelConnector<SelectionModel.Multi<JsonObject>> { + + private Multi<JsonObject> selectionModel = createSelectionModel(); + private SpaceSelectHandler<JsonObject> spaceHandler; + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(selectionModel); + spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid()); + } + + @Override + public void onUnregister() { + spaceHandler.removeHandler(); + } + + @Override + protected Multi<JsonObject> createSelectionModel() { + return new MultiSelectionModel(); + } + + @Override + public MultiSelectionModelState getState() { + return (MultiSelectionModelState) super.getState(); + } + + @OnStateChange("allSelected") + void updateSelectAllCheckbox() { + if (selectionModel.getSelectionColumnRenderer() != null) { + HeaderCell cell = getGrid().getDefaultHeaderRow().getCell( + getGrid().getColumn(0)); + CheckBox widget = (CheckBox) cell.getWidget(); + widget.setValue(getState().allSelected, false); + } + } + + protected class MultiSelectionModel extends AbstractSelectionModel + implements SelectionModel.Multi.Batched<JsonObject> { + + private ComplexRenderer<Boolean> renderer = null; + private Set<RowHandle<JsonObject>> selected = new HashSet<RowHandle<JsonObject>>(); + private Set<RowHandle<JsonObject>> deselected = new HashSet<RowHandle<JsonObject>>(); + private HandlerRegistration selectAll; + private HandlerRegistration dataAvailable; + private Range availableRows; + private boolean batchSelect = false; + + @Override + public void setGrid(Grid<JsonObject> grid) { + super.setGrid(grid); + if (grid != null) { + renderer = createSelectionColumnRenderer(grid); + selectAll = getGrid().addSelectAllHandler( + new SelectAllHandler<JsonObject>() { + + @Override + public void onSelectAll( + SelectAllEvent<JsonObject> event) { + selectAll(); + } + }); + dataAvailable = getGrid().addDataAvailableHandler( + new DataAvailableHandler() { + + @Override + public void onDataAvailable(DataAvailableEvent event) { + availableRows = event.getAvailableRows(); + } + }); + } else if (renderer != null) { + renderer.destroy(); + selectAll.removeHandler(); + dataAvailable.removeHandler(); + renderer = null; + } + } + + /** + * Creates a selection column renderer. This method can be overridden to + * use a custom renderer or use {@code null} to disable the selection + * column. + * + * @param grid + * the grid for this selection model + * @return selection column renderer or {@code null} if not needed + */ + protected ComplexRenderer<Boolean> createSelectionColumnRenderer( + Grid<JsonObject> grid) { + return new MultiSelectionRenderer<JsonObject>(grid); + } + + /** + * Selects all available rows, sends request to server to select + * everything. + */ + public void selectAll() { + assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection."; + + DataSource<JsonObject> dataSource = getGrid().getDataSource(); + for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { + final JsonObject row = dataSource.getRow(i); + if (row != null) { + RowHandle<JsonObject> handle = dataSource.getHandle(row); + markAsSelected(handle, true); + } + } + + getRpcProxy(MultiSelectionModelServerRpc.class).selectAll(); + } + + @Override + public Renderer<Boolean> getSelectionColumnRenderer() { + return renderer; + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean select(JsonObject... rows) { + return select(Arrays.asList(rows)); + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean deselect(JsonObject... rows) { + return deselect(Arrays.asList(rows)); + } + + /** + * {@inheritDoc} + * + * @return always {@code true} + */ + @Override + public boolean deselectAll() { + assert !isBeingBatchSelected() : "Can't select all in middle of a batch selection."; + + DataSource<JsonObject> dataSource = getGrid().getDataSource(); + for (int i = availableRows.getStart(); i < availableRows.getEnd(); ++i) { + final JsonObject row = dataSource.getRow(i); + if (row != null) { + RowHandle<JsonObject> handle = dataSource.getHandle(row); + markAsSelected(handle, false); + } + } + + getRpcProxy(MultiSelectionModelServerRpc.class).deselectAll(); + + return true; + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean select(Collection<JsonObject> rows) { + if (rows.isEmpty()) { + return false; + } + + for (JsonObject row : rows) { + RowHandle<JsonObject> rowHandle = getRowHandle(row); + if (markAsSelected(rowHandle, true)) { + selected.add(rowHandle); + } + } + + if (!isBeingBatchSelected()) { + sendSelected(); + } + return true; + } + + /** + * Marks the given row to be selected or deselected. Returns true if the + * value actually changed. + * <p> + * Note: If selection model is in batch select state, the row will be + * pinned on select. + * + * @param row + * row handle + * @param selected + * {@code true} if row should be selected; {@code false} if + * not + * @return {@code true} if selected status changed; {@code false} if not + */ + protected boolean markAsSelected(RowHandle<JsonObject> row, + boolean selected) { + if (selected && !isSelected(row.getRow())) { + row.getRow().put(GridState.JSONKEY_SELECTED, true); + } else if (!selected && isSelected(row.getRow())) { + row.getRow().remove(GridState.JSONKEY_SELECTED); + } else { + return false; + } + + row.updateRow(); + + if (isBeingBatchSelected()) { + row.pin(); + } + return true; + } + + /** + * {@inheritDoc} + * + * @return {@code false} if rows is empty, else {@code true} + */ + @Override + public boolean deselect(Collection<JsonObject> rows) { + if (rows.isEmpty()) { + return false; + } + + for (JsonObject row : rows) { + RowHandle<JsonObject> rowHandle = getRowHandle(row); + if (markAsSelected(rowHandle, false)) { + deselected.add(rowHandle); + } + } + + if (!isBeingBatchSelected()) { + sendDeselected(); + } + return true; + } + + /** + * Sends a deselect RPC call to server-side containing all deselected + * rows. Unpins any pinned rows. + */ + private void sendDeselected() { + getRpcProxy(MultiSelectionModelServerRpc.class).deselect( + getRowKeys(deselected)); + + if (isBeingBatchSelected()) { + for (RowHandle<JsonObject> row : deselected) { + row.unpin(); + } + } + + deselected.clear(); + } + + /** + * Sends a select RPC call to server-side containing all selected rows. + * Unpins any pinned rows. + */ + private void sendSelected() { + getRpcProxy(MultiSelectionModelServerRpc.class).select( + getRowKeys(selected)); + + if (isBeingBatchSelected()) { + for (RowHandle<JsonObject> row : selected) { + row.unpin(); + } + } + + selected.clear(); + } + + private List<String> getRowKeys(Set<RowHandle<JsonObject>> handles) { + List<String> keys = new ArrayList<String>(); + for (RowHandle<JsonObject> handle : handles) { + keys.add(getRowKey(handle.getRow())); + } + return keys; + } + + private Set<JsonObject> getRows(Set<RowHandle<JsonObject>> handles) { + Set<JsonObject> rows = new HashSet<JsonObject>(); + for (RowHandle<JsonObject> handle : handles) { + rows.add(handle.getRow()); + } + return rows; + } + + @Override + public void startBatchSelect() { + assert selected.isEmpty() && deselected.isEmpty() : "Row caches were not clear."; + batchSelect = true; + } + + @Override + public void commitBatchSelect() { + assert batchSelect : "Not batch selecting."; + if (!selected.isEmpty()) { + sendSelected(); + } + + if (!deselected.isEmpty()) { + sendDeselected(); + } + batchSelect = false; + } + + @Override + public boolean isBeingBatchSelected() { + return batchSelect; + } + + @Override + public Collection<JsonObject> getSelectedRowsBatch() { + return Collections.unmodifiableSet(getRows(selected)); + } + + @Override + public Collection<JsonObject> getDeselectedRowsBatch() { + return Collections.unmodifiableSet(getRows(deselected)); + } + } +} diff --git a/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java new file mode 100644 index 0000000000..b540eed6d5 --- /dev/null +++ b/client/src/com/vaadin/client/connectors/NoSelectionModelConnector.java @@ -0,0 +1,42 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModelNone; +import com.vaadin.shared.ui.Connect; +import com.vaadin.ui.Grid.NoSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link NoSelectionModel}. + */ +@Connect(NoSelectionModel.class) +public class NoSelectionModelConnector extends + AbstractSelectionModelConnector<SelectionModel<JsonObject>> { + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(createSelectionModel()); + } + + @Override + protected SelectionModel<JsonObject> createSelectionModel() { + return new SelectionModelNone<JsonObject>(); + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index 627ee74eca..5daa02c3bf 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -17,6 +17,7 @@ package com.vaadin.client.connectors; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import com.vaadin.client.ServerConnector; @@ -64,14 +65,6 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { * @see GridState#JSONKEY_DETAILS_VISIBLE */ void reapplyDetailsVisibility(int rowIndex, JsonObject row); - - /** - * Closes details for a row. - * - * @param rowIndex - * the index of the row for which to close details - */ - void closeDetails(int rowIndex); } public class RpcDataSource extends AbstractRemoteDataSource<JsonObject> { @@ -104,15 +97,28 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { public void resetDataAndSize(int size) { RpcDataSource.this.resetDataAndSize(size); } + + @Override + public void updateRowData(JsonArray rowArray) { + for (int i = 0; i < rowArray.length(); ++i) { + RpcDataSource.this.updateRowData(rowArray.getObject(i)); + } + } }); } private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class); private DetailsListener detailsListener; + private JsonArray droppedRowKeys = Json.createArray(); @Override protected void requestRows(int firstRowIndex, int numberOfRows, RequestRowsCallback<JsonObject> callback) { + if (droppedRowKeys.length() > 0) { + rpcProxy.dropRows(droppedRowKeys); + droppedRowKeys = Json.createArray(); + } + /* * If you're looking at this code because you want to learn how to * use AbstactRemoteDataSource, please look somewhere else instead. @@ -184,23 +190,15 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { } @Override - protected void pinHandle(RowHandleImpl handle) { - // Server only knows if something is pinned or not. No need to pin - // multiple times. - boolean pinnedBefore = handle.isPinned(); - super.pinHandle(handle); - if (!pinnedBefore) { - rpcProxy.setPinned(getRowKey(handle.getRow()), true); - } - } - - @Override protected void unpinHandle(RowHandleImpl handle) { // Row data is no longer available after it has been unpinned. String key = getRowKey(handle.getRow()); super.unpinHandle(handle); if (!handle.isPinned()) { - rpcProxy.setPinned(key, false); + if (indexOfKey(key) == -1) { + // Row out of view has been unpinned. drop it + droppedRowKeys.set(droppedRowKeys.length(), key); + } } } @@ -222,9 +220,25 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { } } + /** + * Updates row data based on row key. + * + * @since 7.6 + * @param row + * new row object + */ + protected void updateRowData(JsonObject row) { + int index = indexOfKey(getRowKey(row)); + if (index >= 0) { + setRowData(index, Collections.singletonList(row)); + } + } + @Override - protected void onDropFromCache(int rowIndex) { - detailsListener.closeDetails(rowIndex); + protected void onDropFromCache(int rowIndex, JsonObject row) { + if (!((RowHandleImpl) getHandle(row)).isPinned()) { + droppedRowKeys.set(droppedRowKeys.length(), getRowKey(row)); + } } } diff --git a/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java new file mode 100644 index 0000000000..7c66903c2c --- /dev/null +++ b/client/src/com/vaadin/client/connectors/SingleSelectionModelConnector.java @@ -0,0 +1,148 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.connectors; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.data.DataSource.RowHandle; +import com.vaadin.client.renderers.Renderer; +import com.vaadin.client.widget.grid.selection.ClickSelectHandler; +import com.vaadin.client.widget.grid.selection.SelectionModel; +import com.vaadin.client.widget.grid.selection.SelectionModel.Single; +import com.vaadin.client.widget.grid.selection.SpaceSelectHandler; +import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.grid.GridState; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState; +import com.vaadin.ui.Grid.SingleSelectionModel; + +import elemental.json.JsonObject; + +/** + * Connector for server-side {@link SingleSelectionModel}. + * + * @since + * @author Vaadin Ltd + */ +@Connect(SingleSelectionModel.class) +public class SingleSelectionModelConnector extends + AbstractSelectionModelConnector<SelectionModel.Single<JsonObject>> { + + private SpaceSelectHandler<JsonObject> spaceHandler; + private ClickSelectHandler<JsonObject> clickHandler; + private Single<JsonObject> selectionModel = createSelectionModel(); + + @Override + protected void extend(ServerConnector target) { + getGrid().setSelectionModel(selectionModel); + spaceHandler = new SpaceSelectHandler<JsonObject>(getGrid()); + clickHandler = new ClickSelectHandler<JsonObject>(getGrid()); + } + + @Override + public SingleSelectionModelState getState() { + return (SingleSelectionModelState) super.getState(); + } + + @Override + public void onUnregister() { + spaceHandler.removeHandler(); + clickHandler.removeHandler(); + + super.onUnregister(); + } + + @Override + protected Single<JsonObject> createSelectionModel() { + return new SingleSelectionModel(); + } + + @OnStateChange("deselectAllowed") + void updateDeselectAllowed() { + selectionModel.setDeselectAllowed(getState().deselectAllowed); + } + + /** + * SingleSelectionModel without a selection column renderer. + */ + public class SingleSelectionModel extends AbstractSelectionModel implements + SelectionModel.Single<JsonObject> { + + private RowHandle<JsonObject> selectedRow; + private boolean deselectAllowed; + + @Override + public Renderer<Boolean> getSelectionColumnRenderer() { + return null; + } + + @Override + public boolean select(JsonObject row) { + boolean changed = false; + if ((row == null && isDeselectAllowed()) + || (row != null && !getRowHandle(row).equals(selectedRow))) { + if (selectedRow != null) { + selectedRow.getRow().remove(GridState.JSONKEY_SELECTED); + selectedRow.updateRow(); + selectedRow.unpin(); + selectedRow = null; + changed = true; + } + + if (row != null) { + selectedRow = getRowHandle(row); + selectedRow.pin(); + selectedRow.getRow().put(GridState.JSONKEY_SELECTED, true); + selectedRow.updateRow(); + changed = true; + } + } + + if (changed) { + getRpcProxy(SingleSelectionModelServerRpc.class).select( + getRowKey(row)); + } + + return changed; + } + + @Override + public boolean deselect(JsonObject row) { + if (getRowHandle(row).equals(selectedRow)) { + select(null); + } + return false; + } + + @Override + public JsonObject getSelectedRow() { + throw new UnsupportedOperationException( + "This client-side selection model " + + getClass().getSimpleName() + + " does not know selected row."); + } + + @Override + public void setDeselectAllowed(boolean deselectAllowed) { + this.deselectAllowed = deselectAllowed; + } + + @Override + public boolean isDeselectAllowed() { + return deselectAllowed; + } + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 459127c9b4..58cd5c5f19 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -16,8 +16,6 @@ package com.vaadin.client.data; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -120,12 +118,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { @Override public T getRow() throws IllegalStateException { - if (isPinned()) { - return row; - } else { - throw new IllegalStateException("The row handle for key " + key - + " was not pinned"); - } + return row; } public boolean isPinned() { @@ -197,7 +190,6 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { private Map<Object, Integer> pinnedCounts = new HashMap<Object, Integer>(); private Map<Object, RowHandleImpl> pinnedRows = new HashMap<Object, RowHandleImpl>(); - protected Collection<T> temporarilyPinnedRows = Collections.emptySet(); // Size not yet known private int size = -1; @@ -287,8 +279,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { * Simple case: no overlap between cached data and needed data. * Clear the cache and request new data */ - indexToRowMap.clear(); - keyToIndexMap.clear(); + dropFromCache(cached); cached = Range.between(0, 0); handleMissingRows(getMaxCacheRange()); @@ -330,25 +321,48 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { private void dropFromCache(Range range) { for (int i = range.getStart(); i < range.getEnd(); i++) { + // Called after dropping from cache. Dropped row is passed as a + // parameter, but is no longer present in the DataSource T removed = indexToRowMap.remove(Integer.valueOf(i)); + onDropFromCache(i, removed); keyToIndexMap.remove(getRowKey(removed)); - - onDropFromCache(i); } } /** - * A hook that can be overridden to do something whenever a row is dropped - * from the cache. + * A hook that can be overridden to do something whenever a row has been + * dropped from the cache. DataSource no longer has anything in the given + * index. + * <p> + * NOTE: This method has been replaced. Override + * {@link #onDropFromCache(int, Object)} instead of this method. * * @since 7.5.0 * @param rowIndex * the index of the dropped row + * @deprecated replaced by {@link #onDropFromCache(int, Object)} */ + @Deprecated protected void onDropFromCache(int rowIndex) { // noop } + /** + * A hook that can be overridden to do something whenever a row has been + * dropped from the cache. DataSource no longer has anything in the given + * index. + * + * @since 7.6 + * @param rowIndex + * the index of the dropped row + * @param removed + * the removed row object + */ + protected void onDropFromCache(int rowIndex, T removed) { + // Call old version as a fallback (someone might have used it) + onDropFromCache(rowIndex); + } + private void handleMissingRows(Range range) { if (range.isEmpty()) { return; @@ -479,6 +493,15 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { * updated before the widget settings. Support for this will be * implemented later on. */ + + // Run a dummy drop from cache for unused rows. + for (int i = 0; i < partition[0].length(); ++i) { + onDropFromCache(i + partition[0].getStart(), rowData.get(i)); + } + + for (int i = 0; i < partition[2].length(); ++i) { + onDropFromCache(i + partition[2].getStart(), rowData.get(i)); + } } // Eventually check whether all needed rows are now available @@ -732,4 +755,12 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { dataChangeHandler.resetDataAndSize(newSize); } } + + protected int indexOfKey(Object rowKey) { + if (!keyToIndexMap.containsKey(rowKey)) { + return -1; + } else { + return keyToIndexMap.get(rowKey); + } + } } diff --git a/client/src/com/vaadin/client/debug/internal/VDebugWindow.java b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java index b543c23e4d..fbc838f861 100644 --- a/client/src/com/vaadin/client/debug/internal/VDebugWindow.java +++ b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java @@ -19,6 +19,8 @@ import java.util.ArrayList; import java.util.Date; import com.google.gwt.core.client.Duration; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.shared.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; @@ -698,22 +700,32 @@ public final class VDebugWindow extends VOverlay { public void init() { show(); - readStoredState(); - Window.addResizeHandler(new com.google.gwt.event.logical.shared.ResizeHandler() { + /* + * Finalize initialization when all entry points have had the chance to + * e.g. register new sections. + */ + Scheduler.get().scheduleFinally(new ScheduledCommand() { + @Override + public void execute() { + readStoredState(); - Timer t = new Timer() { - @Override - public void run() { - applyPositionAndSize(); - } - }; + Window.addResizeHandler(new com.google.gwt.event.logical.shared.ResizeHandler() { - @Override - public void onResize(ResizeEvent event) { - t.cancel(); - // TODO less - t.schedule(1000); + Timer t = new Timer() { + @Override + public void run() { + applyPositionAndSize(); + } + }; + + @Override + public void onResize(ResizeEvent event) { + t.cancel(); + // TODO less + t.schedule(1000); + } + }); } }); } diff --git a/client/src/com/vaadin/client/extensions/ResponsiveConnector.java b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java index 621c69788c..ae330be8f4 100644 --- a/client/src/com/vaadin/client/extensions/ResponsiveConnector.java +++ b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java @@ -205,7 +205,7 @@ public class ResponsiveConnector extends AbstractExtensionConnector implements // Get all the rulesets from the stylesheet var theRules = new Array(); - var IE = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE()(); + var IEOrEdge = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE()() || @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isEdge()(); var IE8 = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE8()(); try { @@ -263,8 +263,8 @@ public class ResponsiveConnector extends AbstractExtensionConnector implements // Array of all of the separate selectors in this ruleset var haystack = rule.selectorText.split(","); - // IE parses CSS like .class[attr="val"] into [attr="val"].class so we need to check for both - var selectorRegEx = IE ? /\[.*\]([\.|#]\S+)/ : /([\.|#]\S+?)\[.*\]/; + // IE/Edge parses CSS like .class[attr="val"] into [attr="val"].class so we need to check for both + var selectorRegEx = IEOrEdge ? /\[.*\]([\.|#]\S+)/ : /([\.|#]\S+?)\[.*\]/; // Loop all the selectors in this ruleset for(var k = 0, len2 = haystack.length; k < len2; k++) { diff --git a/client/src/com/vaadin/client/ui/AbstractConnector.java b/client/src/com/vaadin/client/ui/AbstractConnector.java index a20c3463c2..7f8d6c2b14 100644 --- a/client/src/com/vaadin/client/ui/AbstractConnector.java +++ b/client/src/com/vaadin/client/ui/AbstractConnector.java @@ -25,7 +25,7 @@ import java.util.Set; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerManager; -import com.google.web.bindery.event.shared.HandlerRegistration; +import com.google.gwt.event.shared.HandlerRegistration; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.FastStringMap; import com.vaadin.client.FastStringSet; diff --git a/client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java b/client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java new file mode 100644 index 0000000000..8272f99d25 --- /dev/null +++ b/client/src/com/vaadin/client/ui/ConnectorFocusAndBlurHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.ui; + +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.EventHelper; +import com.vaadin.client.communication.StateChangeEvent; +import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.shared.EventId; +import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; + +/** + * A handler for focus and blur events which uses {@link FocusAndBlurServerRpc} + * to transmit received events to the server. Events are only handled if there + * is a corresponding listener on the server side. + * + * @since 7.6 + * @author Vaadin Ltd + */ +public class ConnectorFocusAndBlurHandler implements StateChangeHandler, + FocusHandler, BlurHandler { + + private final AbstractComponentConnector connector; + private final Widget widget; + private HandlerRegistration focusRegistration = null; + private HandlerRegistration blurRegistration = null; + + public static void addHandlers(AbstractComponentConnector connector) { + addHandlers(connector, connector.getWidget()); + } + + public static void addHandlers(AbstractComponentConnector connector, + Widget widget) { + connector.addStateChangeHandler("registeredEventListeners", + new ConnectorFocusAndBlurHandler(connector, widget)); + } + + private ConnectorFocusAndBlurHandler(AbstractComponentConnector connector, + Widget widget) { + this.connector = connector; + this.widget = widget; + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + focusRegistration = EventHelper.updateHandler(connector, this, + EventId.FOCUS, focusRegistration, FocusEvent.getType(), widget); + blurRegistration = EventHelper.updateHandler(connector, this, + EventId.BLUR, blurRegistration, BlurEvent.getType(), widget); + } + + @Override + public void onFocus(FocusEvent event) { + // updateHandler ensures that this is called only when + // there is a listener on the server side + getRpc().focus(); + } + + @Override + public void onBlur(BlurEvent event) { + // updateHandler ensures that this is called only when + // there is a listener on the server side + getRpc().blur(); + } + + private FocusAndBlurServerRpc getRpc() { + return connector.getRpcProxy(FocusAndBlurServerRpc.class); + } +} diff --git a/client/src/com/vaadin/client/ui/FocusableScrollPanel.java b/client/src/com/vaadin/client/ui/FocusableScrollPanel.java index 9dd9c17675..1ac5a08ccd 100644 --- a/client/src/com/vaadin/client/ui/FocusableScrollPanel.java +++ b/client/src/com/vaadin/client/ui/FocusableScrollPanel.java @@ -74,7 +74,7 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements style.setPosition(Position.FIXED); style.setTop(0, Unit.PX); style.setLeft(0, Unit.PX); - if (browserInfo.isIE()) { + if (browserInfo.isIE() || browserInfo.isEdge()) { // for #15294: artificially hide little bit more the // focusElement, otherwise IE will make the window to scroll // into it when focused diff --git a/client/src/com/vaadin/client/ui/VButton.java b/client/src/com/vaadin/client/ui/VButton.java index bf321f7f00..2eb967c4fa 100644 --- a/client/src/com/vaadin/client/ui/VButton.java +++ b/client/src/com/vaadin/client/ui/VButton.java @@ -23,7 +23,6 @@ import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.FocusWidget; @@ -94,8 +93,6 @@ public class VButton extends FocusWidget implements ClickHandler { /** For internal use only. May be removed or replaced in the future. */ public int clickShortcut = 0; - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; private long lastClickTime = 0; public VButton() { diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java index 7951759fa2..cf03382333 100644 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ b/client/src/com/vaadin/client/ui/VFilterSelect.java @@ -65,6 +65,7 @@ import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ComputedStyle; import com.vaadin.client.ConnectorMap; +import com.vaadin.client.DeferredWorker; import com.vaadin.client.Focusable; import com.vaadin.client.UIDL; import com.vaadin.client.VConsole; @@ -90,7 +91,7 @@ import com.vaadin.shared.util.SharedUtil; public class VFilterSelect extends Composite implements Field, KeyDownHandler, KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable, SubPartAware, HandlesAriaCaption, HandlesAriaInvalid, - HandlesAriaRequired { + HandlesAriaRequired, DeferredWorker { /** * Represents a suggestion in the suggestion popup box @@ -417,7 +418,9 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, selectPrevPage(); } else { - selectItem(menu.getItems().get(menu.getItems().size() - 1)); + if (!menu.getItems().isEmpty()) { + selectLastItem(); + } } } @@ -596,7 +599,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, + "]"); Element menuFirstChild = menu.getElement().getFirstChildElement(); - final int naturalMenuWidth = menuFirstChild.getOffsetWidth(); + final int naturalMenuWidth = WidgetUtil + .getRequiredWidth(menuFirstChild); if (popupOuterPadding == -1) { popupOuterPadding = WidgetUtil @@ -608,13 +612,21 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, menuFirstChild.getStyle().setWidth(100, Unit.PCT); } - if (BrowserInfo.get().isIE()) { + if (BrowserInfo.get().isIE() + && BrowserInfo.get().getBrowserMajorVersion() < 11) { + // Must take margin,border,padding manually into account for + // menu element as we measure the element child and set width to + // the element parent + double naturalMenuOuterWidth = WidgetUtil + .getRequiredWidthDouble(menuFirstChild) + + getMarginBorderPaddingWidth(menu.getElement()); + /* * IE requires us to specify the width for the container * element. Otherwise it will be 100% wide */ - int rootWidth = Math.max(desiredWidth, naturalMenuWidth) - - popupOuterPadding; + double rootWidth = Math.max(desiredWidth - popupOuterPadding, + naturalMenuOuterWidth); getContainerElement().getStyle().setWidth(rootWidth, Unit.PX); } @@ -1280,6 +1292,12 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, sinkEvents(Event.ONPASTE); } + private static double getMarginBorderPaddingWidth(Element element) { + final ComputedStyle s = new ComputedStyle(element); + return s.getMarginWidth() + s.getBorderWidth() + s.getPaddingWidth(); + + } + /* * (non-Javadoc) * @@ -2185,11 +2203,15 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, @Override public com.google.gwt.user.client.Element getSubPartElement(String subPart) { - if ("textbox".equals(subPart)) { + String[] parts = subPart.split("/"); + if ("textbox".equals(parts[0])) { return tb.getElement(); - } else if ("button".equals(subPart)) { + } else if ("button".equals(parts[0])) { return popupOpener.getElement(); - } else if ("popup".equals(subPart) && suggestionPopup.isAttached()) { + } else if ("popup".equals(parts[0]) && suggestionPopup.isAttached()) { + if (parts.length == 2) { + return suggestionPopup.menu.getSubPartElement(parts[1]); + } return suggestionPopup.getElement(); } return null; @@ -2233,4 +2255,10 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, selectPopupItemWhenResponseIsReceived = Select.NONE; } + @Override + public boolean isWorkPending() { + return waitingForFilteringResponse + || suggestionPopup.lazyPageScroller.isRunning(); + } + } diff --git a/client/src/com/vaadin/client/ui/VFormLayout.java b/client/src/com/vaadin/client/ui/VFormLayout.java index 84a9b4f3dd..bcbb3ebf7b 100644 --- a/client/src/com/vaadin/client/ui/VFormLayout.java +++ b/client/src/com/vaadin/client/ui/VFormLayout.java @@ -22,7 +22,6 @@ import java.util.List; import com.google.gwt.aria.client.Roles; import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.DOM; @@ -327,22 +326,6 @@ public class VFormLayout extends SimplePanel { requiredFieldIndicator = null; } } - - // Workaround for IE weirdness, sometimes returns bad height in some - // circumstances when Caption is empty. See #1444 - // IE7 bugs more often. I wonder what happens when IE8 arrives... - // FIXME: This could be unnecessary for IE8+ - if (BrowserInfo.get().isIE()) { - if (isEmpty) { - setHeight("0px"); - getElement().getStyle().setOverflow(Overflow.HIDDEN); - } else { - setHeight(""); - getElement().getStyle().clearOverflow(); - } - - } - } /** diff --git a/client/src/com/vaadin/client/ui/VRichTextArea.java b/client/src/com/vaadin/client/ui/VRichTextArea.java index 3f63f38067..cb4482f7cb 100644 --- a/client/src/com/vaadin/client/ui/VRichTextArea.java +++ b/client/src/com/vaadin/client/ui/VRichTextArea.java @@ -322,7 +322,7 @@ public class VRichTextArea extends Composite implements Field, KeyPressHandler, if ("<br>".equals(result)) { result = ""; } - } else if (browser.isWebkit()) { + } else if (browser.isWebkit() || browser.isEdge()) { if ("<br>".equals(result) || "<div><br></div>".equals(result)) { result = ""; } diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index 2724daae77..6bb8f063a6 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -1295,8 +1295,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (uidl.hasVariable("selected")) { final Set<String> selectedKeys = uidl .getStringArrayVariableAsSet("selected"); - removeUnselectedRowKeys(selectedKeys); - + // Do not update focus if there is a single selected row + // that is the same as the previous selection. This prevents + // unwanted scrolling (#18247). + boolean rowsUnSelected = removeUnselectedRowKeys(selectedKeys); + boolean updateFocus = rowsUnSelected || selectedRowKeys.size() == 0 + || focusedRow == null; if (scrollBody != null) { Iterator<Widget> iterator = scrollBody.iterator(); while (iterator.hasNext()) { @@ -1313,7 +1317,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, selected = true; keyboardSelectionOverRowFetchInProgress = true; } - if (selected && selectedKeys.size() == 1) { + if (selected && selectedKeys.size() == 1 && updateFocus) { /* * If a single item is selected, move focus to the * selected row. (#10522) @@ -1338,14 +1342,14 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return keyboardSelectionOverRowFetchInProgress; } - private void removeUnselectedRowKeys(final Set<String> selectedKeys) { + private boolean removeUnselectedRowKeys(final Set<String> selectedKeys) { List<String> unselectedKeys = new ArrayList<String>(0); for (String key : selectedRowKeys) { if (!selectedKeys.contains(key)) { unselectedKeys.add(key); } } - selectedRowKeys.removeAll(unselectedKeys); + return selectedRowKeys.removeAll(unselectedKeys); } /** For internal use only. May be removed or replaced in the future. */ @@ -3629,6 +3633,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } } else { c.setText(caption); + if (BrowserInfo.get().isIE10()) { + // IE10 can some times define min-height to include + // padding when setting the text... + // See https://dev.vaadin.com/ticket/15169 + WidgetUtil.forceIERedraw(c.getElement()); + } } c.setSorted(false); diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index ded9977f5e..e196870348 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -61,11 +61,12 @@ import com.google.gwt.user.client.ui.impl.FocusImpl; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ComputedStyle; import com.vaadin.client.Focusable; import com.vaadin.client.TooltipInfo; -import com.vaadin.client.WidgetUtil; import com.vaadin.client.VCaption; import com.vaadin.client.VTooltip; +import com.vaadin.client.WidgetUtil; import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.ComponentConstants; @@ -1227,8 +1228,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, SubPartAware public void updateContentNodeHeight() { if (!isDynamicHeight()) { int contentHeight = getOffsetHeight(); - contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight"); + contentHeight -= deco.getOffsetHeight(); contentHeight -= tb.getOffsetHeight(); + + ComputedStyle cs = new ComputedStyle(contentNode); + contentHeight -= Math.ceil(cs.getPaddingHeight()); + contentHeight -= Math.ceil(cs.getBorderHeight()); + if (contentHeight < 0) { contentHeight = 0; } diff --git a/client/src/com/vaadin/client/ui/VTextArea.java b/client/src/com/vaadin/client/ui/VTextArea.java index 50930f2fee..bb3d3a476b 100644 --- a/client/src/com/vaadin/client/ui/VTextArea.java +++ b/client/src/com/vaadin/client/ui/VTextArea.java @@ -224,16 +224,16 @@ public class VTextArea extends VTextField implements DragImageModifier { protected boolean browserSupportsMaxLengthAttribute() { BrowserInfo info = BrowserInfo.get(); - if (info.isFirefox() && info.isBrowserVersionNewerOrEqual(4, 0)) { + if (info.isFirefox()) { return true; } - if (info.isSafari() && info.isBrowserVersionNewerOrEqual(5, 0)) { + if (info.isSafari()) { return true; } - if (info.isIE() && info.isBrowserVersionNewerOrEqual(10, 0)) { + if (info.isIE10() || info.isIE11() || info.isEdge()) { return true; } - if (info.isAndroid() && info.isBrowserVersionNewerOrEqual(2, 3)) { + if (info.isAndroid()) { return true; } return false; diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java index 8729de4a43..846b16d0cb 100644 --- a/client/src/com/vaadin/client/ui/VTree.java +++ b/client/src/com/vaadin/client/ui/VTree.java @@ -179,7 +179,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, @Override public void execute() { - Util.notifyParentOfSizeChange(VTree.this, true); + doLayout(); } }); @@ -969,7 +969,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, open = state; if (!rendering) { - Util.notifyParentOfSizeChange(VTree.this, false); + doLayout(); } } @@ -2239,4 +2239,15 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, com.google.gwt.user.client.Element captionElement) { AriaHelper.bindCaption(body, captionElement); } + + /** + * Tell LayoutManager that a layout is needed later for this VTree + */ + private void doLayout() { + // IE8 needs a hack to measure the tree again after update + WidgetUtil.forceIE8Redraw(getElement()); + + // This calls LayoutManager setNeedsMeasure and layoutNow + Util.notifyParentOfSizeChange(this, false); + } } diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java index 0c1b83ab0f..963d83a6e6 100644 --- a/client/src/com/vaadin/client/ui/VUI.java +++ b/client/src/com/vaadin/client/ui/VUI.java @@ -18,7 +18,6 @@ package com.vaadin.client.ui; import java.util.ArrayList; -import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.HasScrollHandlers; @@ -44,8 +43,8 @@ import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.LayoutManager; import com.vaadin.client.Profiler; -import com.vaadin.client.WidgetUtil; import com.vaadin.client.VConsole; +import com.vaadin.client.WidgetUtil; import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; import com.vaadin.client.ui.ui.UIConnector; @@ -515,13 +514,6 @@ public class VUI extends SimplePanel implements ResizeHandler, public void focusStoredElement() { if (storedFocus != null) { storedFocus.focus(); - - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - storedFocus.focus(); - } - }); } } diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index c5cab8ff6b..2d2d6ecee1 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -44,8 +44,6 @@ import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.shared.HandlerRegistration; @@ -79,8 +77,7 @@ import com.vaadin.shared.ui.window.WindowRole; * @author Vaadin Ltd */ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, - ScrollHandler, KeyDownHandler, KeyUpHandler, FocusHandler, BlurHandler, - Focusable { + ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable { private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>(); @@ -221,7 +218,6 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, constructDOM(); contentPanel.addScrollHandler(this); contentPanel.addKeyDownHandler(this); - contentPanel.addKeyUpHandler(this); contentPanel.addFocusHandler(this); contentPanel.addBlurHandler(this); } @@ -562,17 +558,10 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } private static void focusTopmostModalWindow() { - // If we call focus() directly without scheduling, it does not work in - // IE and FF. - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - VWindow topmost = getTopmostWindow(); - if ((topmost != null) && (topmost.vaadinModality)) { - topmost.focus(); - } - } - }); + VWindow topmost = getTopmostWindow(); + if ((topmost != null) && (topmost.vaadinModality)) { + topmost.focus(); + } } @Override @@ -762,11 +751,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, modalityCurtain.removeFromParent(); - if (BrowserInfo.get().isIE()) { - // IE leaks memory in certain cases unless we release the reference - // (#9197) - modalityCurtain = null; - } + // IE leaks memory in certain cases unless we release the reference + // (#9197) + modalityCurtain = null; } /* @@ -1353,13 +1340,6 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } @Override - public void onKeyUp(KeyUpEvent event) { - if (isClosable() && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { - onCloseClick(); - } - } - - @Override public void onBlur(BlurEvent event) { if (client.hasEventListeners(this, EventId.BLUR)) { client.updateVariable(id, EventId.BLUR, "", true); @@ -1375,7 +1355,11 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, @Override public void focus() { - contentPanel.focus(); + // We don't want to use contentPanel.focus() as that will use a timer in + // Chrome/Safari and ultimately run focus events in the wrong order when + // opening a modal window and focusing some other component at the same + // time + contentPanel.getElement().focus(); } private int getDecorationHeight() { diff --git a/client/src/com/vaadin/client/ui/button/ButtonConnector.java b/client/src/com/vaadin/client/ui/button/ButtonConnector.java index 2d13d62a91..2c2006e19b 100644 --- a/client/src/com/vaadin/client/ui/button/ButtonConnector.java +++ b/client/src/com/vaadin/client/ui/button/ButtonConnector.java @@ -16,24 +16,17 @@ package com.vaadin.client.ui.button; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; -import com.vaadin.client.EventHelper; import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.VCaption; import com.vaadin.client.annotations.OnStateChange; -import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VButton; import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.Connect.LoadStyle; import com.vaadin.shared.ui.button.ButtonServerRpc; @@ -42,10 +35,7 @@ import com.vaadin.ui.Button; @Connect(value = Button.class, loadStyle = LoadStyle.EAGER) public class ButtonConnector extends AbstractComponentConnector implements - BlurHandler, FocusHandler, ClickHandler { - - private HandlerRegistration focusHandlerRegistration = null; - private HandlerRegistration blurHandlerRegistration = null; + ClickHandler { @Override public boolean delegateCaptionHandling() { @@ -57,6 +47,7 @@ public class ButtonConnector extends AbstractComponentConnector implements super.init(); getWidget().addClickHandler(this); getWidget().client = getConnection(); + ConnectorFocusAndBlurHandler.addHandlers(this); } @OnStateChange("errorMessage") @@ -90,15 +81,6 @@ public class ButtonConnector extends AbstractComponentConnector implements } } - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - focusHandlerRegistration = EventHelper.updateFocusHandler(this, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, - blurHandlerRegistration); - } - @OnStateChange({ "caption", "captionAsHtml" }) void setCaption() { VCaption.setCaptionText(getWidget().captionElement, getState()); @@ -127,20 +109,6 @@ public class ButtonConnector extends AbstractComponentConnector implements } @Override - public void onFocus(FocusEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).focus(); - } - - @Override - public void onBlur(BlurEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).blur(); - } - - @Override public void onClick(ClickEvent event) { if (getState().disableOnClick) { // Simulate getting disabled from the server without waiting for the diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java index 1a54fe0454..55834397d3 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java @@ -268,8 +268,13 @@ public class DateCellDayEvent extends FocusableHTML implements } int endX = event.getClientX(); int endY = event.getClientY(); - int xDiff = startX - endX; - int yDiff = startY - endY; + int xDiff = 0, yDiff = 0; + if (startX != -1 && startY != -1) { + // Drag started + xDiff = startX - endX; + yDiff = startY - endY; + } + startX = -1; startY = -1; mouseMoveStarted = false; diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java index 3bf6930933..158241337b 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java @@ -392,8 +392,11 @@ public class SimpleDayCell extends FocusableFlowPanel implements int endX = event.getClientX(); int endY = event.getClientY(); - int xDiff = startX - endX; - int yDiff = startY - endY; + int xDiff = 0, yDiff = 0; + if (startX != -1 && startY != -1) { + xDiff = startX - endX; + yDiff = startY - endY; + } startX = -1; startY = -1; prevDayDiff = 0; diff --git a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java index 9e689f3314..8cfcf7feb1 100644 --- a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java +++ b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java @@ -16,25 +16,19 @@ package com.vaadin.client.ui.checkbox; import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; -import com.vaadin.client.EventHelper; import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.client.VCaption; import com.vaadin.client.VTooltip; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractFieldConnector; +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VCheckBox; import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.checkbox.CheckBoxServerRpc; import com.vaadin.shared.ui.checkbox.CheckBoxState; @@ -42,10 +36,7 @@ import com.vaadin.ui.CheckBox; @Connect(CheckBox.class) public class CheckBoxConnector extends AbstractFieldConnector implements - FocusHandler, BlurHandler, ClickHandler { - - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; + ClickHandler { @Override public boolean delegateCaptionHandling() { @@ -55,21 +46,18 @@ public class CheckBoxConnector extends AbstractFieldConnector implements @Override protected void init() { super.init(); + getWidget().addClickHandler(this); getWidget().client = getConnection(); getWidget().id = getConnectorId(); + ConnectorFocusAndBlurHandler.addHandlers(this); } @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); - focusHandlerRegistration = EventHelper.updateFocusHandler(this, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, - blurHandlerRegistration); - if (null != getState().errorMessage) { getWidget().setAriaInvalid(true); @@ -127,20 +115,6 @@ public class CheckBoxConnector extends AbstractFieldConnector implements } @Override - public void onFocus(FocusEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).focus(); - } - - @Override - public void onBlur(BlurEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).blur(); - } - - @Override public void onClick(ClickEvent event) { if (!isEnabled()) { return; diff --git a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java index 2aae9beae6..65d4a1eb9b 100644 --- a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java +++ b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java @@ -15,30 +15,20 @@ */ package com.vaadin.client.ui.nativebutton; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; -import com.vaadin.client.EventHelper; import com.vaadin.client.VCaption; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VNativeButton; -import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.button.ButtonServerRpc; import com.vaadin.shared.ui.button.NativeButtonState; import com.vaadin.ui.NativeButton; @Connect(NativeButton.class) -public class NativeButtonConnector extends AbstractComponentConnector implements - BlurHandler, FocusHandler { - - private HandlerRegistration focusHandlerRegistration; - private HandlerRegistration blurHandlerRegistration; +public class NativeButtonConnector extends AbstractComponentConnector { @Override public void init() { @@ -47,6 +37,8 @@ public class NativeButtonConnector extends AbstractComponentConnector implements getWidget().buttonRpcProxy = getRpcProxy(ButtonServerRpc.class); getWidget().client = getConnection(); getWidget().paintableId = getConnectorId(); + + ConnectorFocusAndBlurHandler.addHandlers(this); } @Override @@ -59,10 +51,6 @@ public class NativeButtonConnector extends AbstractComponentConnector implements super.onStateChanged(stateChangeEvent); getWidget().disableOnClick = getState().disableOnClick; - focusHandlerRegistration = EventHelper.updateFocusHandler(this, - focusHandlerRegistration); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, - blurHandlerRegistration); // Set text VCaption.setCaptionText(getWidget(), getState()); @@ -107,19 +95,4 @@ public class NativeButtonConnector extends AbstractComponentConnector implements public NativeButtonState getState() { return (NativeButtonState) super.getState(); } - - @Override - public void onFocus(FocusEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).focus(); - } - - @Override - public void onBlur(BlurEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).blur(); - } - } diff --git a/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java b/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java index 938903da9a..d6ff2015b4 100644 --- a/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java +++ b/client/src/com/vaadin/client/ui/nativeselect/NativeSelectConnector.java @@ -16,55 +16,23 @@ package com.vaadin.client.ui.nativeselect; -import com.google.gwt.event.dom.client.BlurEvent; -import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.FocusEvent; -import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.event.shared.HandlerRegistration; -import com.vaadin.client.EventHelper; -import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.ui.ConnectorFocusAndBlurHandler; import com.vaadin.client.ui.VNativeSelect; import com.vaadin.client.ui.optiongroup.OptionGroupBaseConnector; -import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; import com.vaadin.shared.ui.Connect; import com.vaadin.ui.NativeSelect; @Connect(NativeSelect.class) -public class NativeSelectConnector extends OptionGroupBaseConnector implements - BlurHandler, FocusHandler { +public class NativeSelectConnector extends OptionGroupBaseConnector { - private HandlerRegistration focusHandlerRegistration = null; - private HandlerRegistration blurHandlerRegistration = null; - - public NativeSelectConnector() { - super(); - } - - @OnStateChange("registeredEventListeners") - private void onServerEventListenerChanged() { - focusHandlerRegistration = EventHelper.updateFocusHandler(this, - focusHandlerRegistration, getWidget().getSelect()); - blurHandlerRegistration = EventHelper.updateBlurHandler(this, - blurHandlerRegistration, getWidget().getSelect()); + @Override + protected void init() { + super.init(); + ConnectorFocusAndBlurHandler.addHandlers(this, getWidget().getSelect()); } @Override public VNativeSelect getWidget() { return (VNativeSelect) super.getWidget(); } - - @Override - public void onFocus(FocusEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).focus(); - } - - @Override - public void onBlur(BlurEvent event) { - // EventHelper.updateFocusHandler ensures that this is called only when - // there is a listener on server side - getRpcProxy(FocusAndBlurServerRpc.class).blur(); - } - } diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java index fc3e6ca0fc..23091d0ad5 100644 --- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java +++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java @@ -27,8 +27,8 @@ import com.vaadin.client.BrowserInfo; import com.vaadin.client.Paintable; import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; -import com.vaadin.client.WidgetUtil; import com.vaadin.client.VConsole; +import com.vaadin.client.WidgetUtil; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.VTree; @@ -62,6 +62,10 @@ public class TreeConnector extends AbstractComponentConnector implements if (uidl.hasAttribute("partialUpdate")) { handleUpdate(uidl); + + // IE8 needs a hack to measure the tree again after update + WidgetUtil.forceIE8Redraw(getWidget().getElement()); + getWidget().rendering = false; return; } diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java index d67c953c6e..f5656dfdc4 100644 --- a/client/src/com/vaadin/client/ui/ui/UIConnector.java +++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java @@ -59,6 +59,7 @@ import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.ServerConnector; import com.vaadin.client.UIDL; +import com.vaadin.client.Util; import com.vaadin.client.VConsole; import com.vaadin.client.ValueMap; import com.vaadin.client.annotations.OnStateChange; @@ -71,6 +72,7 @@ import com.vaadin.client.ui.ShortcutActionHandler; import com.vaadin.client.ui.VNotification; import com.vaadin.client.ui.VOverlay; import com.vaadin.client.ui.VUI; +import com.vaadin.client.ui.VWindow; import com.vaadin.client.ui.layout.MayScrollChildren; import com.vaadin.client.ui.window.WindowConnector; import com.vaadin.server.Page.Styles; @@ -319,19 +321,19 @@ public class UIConnector extends AbstractSingleComponentContainerConnector Scheduler.get().scheduleDeferred(new Command() { @Override public void execute() { - ComponentConnector paintable = (ComponentConnector) uidl + ComponentConnector connector = (ComponentConnector) uidl .getPaintableAttribute("focused", getConnection()); - if (paintable == null) { + if (connector == null) { // Do not try to focus invisible components which not // present in UIDL return; } - final Widget toBeFocused = paintable.getWidget(); + final Widget toBeFocused = connector.getWidget(); /* * Two types of Widgets can be focused, either implementing - * GWT HasFocus of a thinner Vaadin specific Focusable + * GWT Focusable of a thinner Vaadin specific Focusable * interface. */ if (toBeFocused instanceof com.google.gwt.user.client.ui.Focusable) { @@ -340,7 +342,14 @@ public class UIConnector extends AbstractSingleComponentContainerConnector } else if (toBeFocused instanceof Focusable) { ((Focusable) toBeFocused).focus(); } else { - VConsole.log("Could not focus component"); + getLogger() + .severe("Server is trying to set focus to the widget of connector " + + Util.getConnectorString(connector) + + " but it is not focusable. The widget should implement either " + + com.google.gwt.user.client.ui.Focusable.class + .getName() + + " or " + + Focusable.class.getName()); } } }); @@ -669,6 +678,19 @@ public class UIConnector extends AbstractSingleComponentContainerConnector if (c instanceof WindowConnector) { WindowConnector wc = (WindowConnector) c; wc.setWindowOrderAndPosition(); + VWindow window = wc.getWidget(); + if (!window.isAttached()) { + + // Attach so that all widgets inside the Window are attached + // when their onStateChange is run + + // Made invisible here for legacy reasons and made visible + // at the end of stateChange. This dance could probably be + // removed + window.setVisible(false); + window.show(); + } + } } @@ -752,8 +774,7 @@ public class UIConnector extends AbstractSingleComponentContainerConnector getState().pushConfiguration.mode.isEnabled()); } if (stateChangeEvent.hasPropertyChanged("reconnectDialogConfiguration")) { - getConnection().getConnectionStateHandler() - .configurationUpdated(); + getConnection().getConnectionStateHandler().configurationUpdated(); } if (stateChangeEvent.hasPropertyChanged("overlayContainerLabel")) { @@ -1022,59 +1043,16 @@ public class UIConnector extends AbstractSingleComponentContainerConnector } - forceStateChangeRecursively(UIConnector.this); - // UIDL has no stored URL which we can repaint so we do some find and - // replace magic... - String newThemeBase = getConnection().translateVaadinUri("theme://"); - replaceThemeAttribute(oldThemeBase, newThemeBase); + // Request a full resynchronization from the server to deal with legacy + // components + getConnection().getMessageSender().resynchronize(); + // Immediately update state and do layout while waiting for the resync + forceStateChangeRecursively(UIConnector.this); getLayoutManager().forceLayout(); } /** - * Finds all attributes where theme:// urls have possibly been used and - * replaces any old theme url with a new one - * - * @param oldPrefix - * The start of the old theme URL - * @param newPrefix - * The start of the new theme URL - */ - private void replaceThemeAttribute(String oldPrefix, String newPrefix) { - // Images - replaceThemeAttribute("src", oldPrefix, newPrefix); - // Embedded flash - replaceThemeAttribute("value", oldPrefix, newPrefix); - replaceThemeAttribute("movie", oldPrefix, newPrefix); - } - - /** - * Finds any attribute of the given type where theme:// urls have possibly - * been used and replaces any old theme url with a new one - * - * @param attributeName - * The name of the attribute, e.g. "src" - * @param oldPrefix - * The start of the old theme URL - * @param newPrefix - * The start of the new theme URL - */ - private void replaceThemeAttribute(String attributeName, String oldPrefix, - String newPrefix) { - // Find all "attributeName=" which start with "oldPrefix" using e.g. - // [^src='http://oldpath'] - NodeList<Element> elements = querySelectorAll("[" + attributeName - + "^='" + oldPrefix + "']"); - for (int i = 0; i < elements.getLength(); i++) { - Element element = elements.getItem(i); - element.setAttribute( - attributeName, - element.getAttribute(attributeName).replace(oldPrefix, - newPrefix)); - } - } - - /** * Force a full recursive recheck of every connector's state variables. * * @see #forceStateChange() diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java index 9b710981d8..9ea3c8bb68 100644 --- a/client/src/com/vaadin/client/ui/window/WindowConnector.java +++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java @@ -17,6 +17,8 @@ package com.vaadin.client.ui.window; import java.util.logging.Logger; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; @@ -354,10 +356,6 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector if (state.modal != window.vaadinModality) { window.setVaadinModality(!window.vaadinModality); } - if (!window.isAttached()) { - window.setVisible(false); // hide until possible centering - window.show(); - } boolean resizeable = state.resizable && state.windowMode == WindowMode.NORMAL; window.setResizable(resizeable); @@ -407,7 +405,13 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector window.centered = state.centered; // Ensure centering before setting visible (#16486) if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) { - window.center(); + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + getWidget().center(); + } + }); } window.setVisible(true); diff --git a/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java b/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java new file mode 100644 index 0000000000..564b0f1a03 --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/DefaultEditorEventHandler.java @@ -0,0 +1,225 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid; + +import com.google.gwt.core.client.Duration; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.user.client.Event; +import com.vaadin.client.WidgetUtil; +import com.vaadin.client.ui.FocusUtil; +import com.vaadin.client.widget.grid.events.EditorMoveEvent; +import com.vaadin.client.widget.grid.events.EditorOpenEvent; +import com.vaadin.client.widgets.Grid.Editor; +import com.vaadin.client.widgets.Grid.EditorDomEvent; + +/** + * The default handler for Grid editor events. Offers several overridable + * protected methods for easier customization. + * + * @since + * @author Vaadin Ltd + */ +public class DefaultEditorEventHandler<T> implements Editor.EventHandler<T> { + + public static final int KEYCODE_OPEN = KeyCodes.KEY_ENTER; + public static final int KEYCODE_MOVE = KeyCodes.KEY_ENTER; + public static final int KEYCODE_CLOSE = KeyCodes.KEY_ESCAPE; + + private double lastTouchEventTime = 0; + private int lastTouchEventX = -1; + private int lastTouchEventY = -1; + private int lastTouchEventRow = -1; + + /** + * Returns whether the given event is a touch event that should open the + * editor. + * + * @param event + * the received event + * @return whether the event is a touch open event + */ + protected boolean isTouchOpenEvent(EditorDomEvent<T> event) { + final Event e = event.getDomEvent(); + final int type = e.getTypeInt(); + + final double now = Duration.currentTimeMillis(); + final int currentX = WidgetUtil.getTouchOrMouseClientX(e); + final int currentY = WidgetUtil.getTouchOrMouseClientY(e); + + final boolean validTouchOpenEvent = type == Event.ONTOUCHEND + && now - lastTouchEventTime < 500 + && lastTouchEventRow == event.getCell().getRowIndex() + && Math.abs(lastTouchEventX - currentX) < 20 + && Math.abs(lastTouchEventY - currentY) < 20; + + if (type == Event.ONTOUCHSTART) { + lastTouchEventX = currentX; + lastTouchEventY = currentY; + } + + if (type == Event.ONTOUCHEND) { + lastTouchEventTime = now; + lastTouchEventRow = event.getCell().getRowIndex(); + } + + return validTouchOpenEvent; + } + + /** + * Returns whether the given event should open the editor. The default + * implementation returns true if and only if the event is a doubleclick or + * if it is a keydown event and the keycode is {@link #KEYCODE_OPEN}. + * + * @param event + * the received event + * @return true if the event is an open event, false otherwise + */ + protected boolean isOpenEvent(EditorDomEvent<T> event) { + final Event e = event.getDomEvent(); + return e.getTypeInt() == Event.ONDBLCLICK + || (e.getTypeInt() == Event.ONKEYDOWN && e.getKeyCode() == KEYCODE_OPEN) + || isTouchOpenEvent(event); + } + + /** + * Opens the editor on the appropriate row if the received event is an open + * event. The default implementation uses + * {@link #isOpenEvent(EditorDomEvent) isOpenEvent}. + * + * @param event + * the received event + * @return true if this method handled the event and nothing else should be + * done, false otherwise + */ + protected boolean handleOpenEvent(EditorDomEvent<T> event) { + if (isOpenEvent(event)) { + final EventCellReference<T> cell = event.getCell(); + + editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM()); + + // FIXME should be in editRow + event.getGrid().fireEvent(new EditorOpenEvent(cell)); + + event.getDomEvent().preventDefault(); + + return true; + } + return false; + } + + /** + * Moves the editor to another row if the received event is a move event. + * The default implementation moves the editor to the clicked row if the + * event is a click; otherwise, if the event is a keydown and the keycode is + * {@link #KEYCODE_MOVE}, moves the editor one row up or down if the shift + * key is pressed or not, respectively. + * + * @param event + * the received event + * @return true if this method handled the event and nothing else should be + * done, false otherwise + */ + protected boolean handleMoveEvent(EditorDomEvent<T> event) { + Event e = event.getDomEvent(); + final EventCellReference<T> cell = event.getCell(); + + // TODO: Move on touch events + if (e.getTypeInt() == Event.ONCLICK) { + + editRow(event, cell.getRowIndex(), cell.getColumnIndexDOM()); + + // FIXME should be in editRow + event.getGrid().fireEvent(new EditorMoveEvent(cell)); + + return true; + } + + else if (e.getTypeInt() == Event.ONKEYDOWN + && e.getKeyCode() == KEYCODE_MOVE) { + + editRow(event, event.getRowIndex() + (e.getShiftKey() ? -1 : +1), + event.getFocusedColumnIndex()); + + // FIXME should be in editRow + event.getGrid().fireEvent(new EditorMoveEvent(cell)); + + return true; + } + + return false; + } + + /** + * Returns whether the given event should close the editor. The default + * implementation returns true if and only if the event is a keydown event + * and the keycode is {@link #KEYCODE_CLOSE}. + * + * @param event + * the received event + * @return true if the event is a close event, false otherwise + */ + protected boolean isCloseEvent(EditorDomEvent<T> event) { + final Event e = event.getDomEvent(); + return e.getTypeInt() == Event.ONKEYDOWN + && e.getKeyCode() == KEYCODE_CLOSE; + } + + /** + * Closes the editor if the received event is a close event. The default + * implementation uses {@link #isCloseEvent(EditorDomEvent) isCloseEvent}. + * + * @param event + * the received event + * @return true if this method handled the event and nothing else should be + * done, false otherwise + */ + protected boolean handleCloseEvent(EditorDomEvent<T> event) { + if (isCloseEvent(event)) { + event.getEditor().cancel(); + FocusUtil.setFocus(event.getGrid(), true); + return true; + } + return false; + } + + protected void editRow(EditorDomEvent<T> event, int rowIndex, int colIndex) { + int rowCount = event.getGrid().getDataSource().size(); + // Limit rowIndex between 0 and rowCount - 1 + rowIndex = Math.max(0, Math.min(rowCount - 1, rowIndex)); + + int colCount = event.getGrid().getVisibleColumns().size(); + // Limit colIndex between 0 and colCount - 1 + colIndex = Math.max(0, Math.min(colCount - 1, colIndex)); + + event.getEditor().editRow(rowIndex, colIndex); + } + + @Override + public boolean handleEvent(EditorDomEvent<T> event) { + final Editor<T> editor = event.getEditor(); + final boolean isBody = event.getCell().isBody(); + + if (event.getGrid().isEditorActive()) { + return (!editor.isBuffered() && isBody && handleMoveEvent(event)) + || handleCloseEvent(event) + // Swallow events if editor is open and buffered (modal) + || editor.isBuffered(); + } else { + return event.getGrid().isEnabled() && isBody + && handleOpenEvent(event); + } + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java new file mode 100644 index 0000000000..99f59aa82a --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorCloseEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.CellReference; + +/** + * Event that gets fired when an open editor is closed (and not reopened + * elsewhere) + */ +public class EditorCloseEvent extends EditorEvent { + + public EditorCloseEvent(CellReference<?> cell) { + super(cell); + } + + @Override + protected void dispatch(EditorEventHandler handler) { + handler.onEditorClose(this); + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java new file mode 100644 index 0000000000..eb34033197 --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorEvent.java @@ -0,0 +1,108 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.GwtEvent; +import com.vaadin.client.widget.grid.CellReference; +import com.vaadin.client.widgets.Grid; +import com.vaadin.client.widgets.Grid.Column; + +/** + * Base class for editor events. + */ +public abstract class EditorEvent extends GwtEvent<EditorEventHandler> { + public static final Type<EditorEventHandler> TYPE = new Type<EditorEventHandler>(); + + private CellReference<?> cell; + + protected EditorEvent(CellReference<?> cell) { + this.cell = cell; + } + + @Override + public Type<EditorEventHandler> getAssociatedType() { + return TYPE; + } + + /** + * Get a reference to the Grid that fired this Event. + * + * @return a Grid reference + */ + @SuppressWarnings("unchecked") + public <T> Grid<T> getGrid() { + return (Grid<T>) cell.getGrid(); + } + + /** + * Get a reference to the cell that was active when this Event was fired. + * NOTE: do <i>NOT</i> rely on this information remaining accurate after + * leaving the event handler. + * + * @return a cell reference + */ + @SuppressWarnings("unchecked") + public <T> CellReference<T> getCell() { + return (CellReference<T>) cell; + } + + /** + * Get a reference to the row that was active when this Event was fired. + * NOTE: do <i>NOT</i> rely on this information remaining accurate after + * leaving the event handler. + * + * @return a row data object + */ + @SuppressWarnings("unchecked") + public <T> T getRow() { + return (T) cell.getRow(); + } + + /** + * Get the index of the row that was active when this Event was fired. NOTE: + * do <i>NOT</i> rely on this information remaining accurate after leaving + * the event handler. + * + * @return an integer value + */ + public int getRowIndex() { + return cell.getRowIndex(); + } + + /** + * Get a reference to the column that was active when this Event was fired. + * NOTE: do <i>NOT</i> rely on this information remaining accurate after + * leaving the event handler. + * + * @return a column object + */ + @SuppressWarnings("unchecked") + public <C, T> Column<C, T> getColumn() { + return (Column<C, T>) cell.getColumn(); + } + + /** + * Get the index of the column that was active when this Event was fired. + * NOTE: do <i>NOT</i> rely on this information remaining accurate after + * leaving the event handler. + * + * @return an integer value + */ + public int getColumnIndex() { + return cell.getColumnIndex(); + } + +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java b/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java new file mode 100644 index 0000000000..4f9396a9f1 --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorEventHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Common handler interface for editor events + */ +public interface EditorEventHandler extends EventHandler { + + /** + * Action to perform when the editor has been opened + * + * @param e + * an editor open event object + */ + public void onEditorOpen(EditorOpenEvent e); + + /** + * Action to perform when the editor is re-opened on another row + * + * @param e + * an editor move event object + */ + public void onEditorMove(EditorMoveEvent e); + + /** + * Action to perform when the editor is closed + * + * @param e + * an editor close event object + */ + public void onEditorClose(EditorCloseEvent e); + +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java new file mode 100644 index 0000000000..0e5e2dcd7b --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorMoveEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.CellReference; + +/** + * Event that gets fired when an already open editor is closed and re-opened on + * another row + */ +public class EditorMoveEvent extends EditorEvent { + + public EditorMoveEvent(CellReference<?> cell) { + super(cell); + } + + @Override + protected void dispatch(EditorEventHandler handler) { + handler.onEditorMove(this); + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java b/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java new file mode 100644 index 0000000000..df0171945f --- /dev/null +++ b/client/src/com/vaadin/client/widget/grid/events/EditorOpenEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client.widget.grid.events; + +import com.vaadin.client.widget.grid.CellReference; + +/** + * Event that gets fired when the editor is opened + */ +public class EditorOpenEvent extends EditorEvent { + + public EditorOpenEvent(CellReference<?> cell) { + super(cell); + } + + @Override + protected void dispatch(EditorEventHandler handler) { + handler.onEditorOpen(this); + } + +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java b/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java index 1e47d3ad6b..c64908f24c 100644 --- a/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java +++ b/client/src/com/vaadin/client/widget/grid/selection/MultiSelectionRenderer.java @@ -56,6 +56,8 @@ import com.vaadin.client.widgets.Grid; public class MultiSelectionRenderer<T> extends ClickableRenderer<Boolean, CheckBox> { + private static final String SELECTION_CHECKBOX_CLASSNAME = "-selection-checkbox"; + /** The size of the autoscroll area, both top and bottom. */ private static final int SCROLL_AREA_GRADIENT_PX = 100; @@ -591,6 +593,8 @@ public class MultiSelectionRenderer<T> extends @Override public CheckBox createWidget() { final CheckBox checkBox = GWT.create(CheckBox.class); + checkBox.setStylePrimaryName(grid.getStylePrimaryName() + + SELECTION_CHECKBOX_CLASSNAME); CheckBoxEventHandler handler = new CheckBoxEventHandler(checkBox); // Sink events diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java index 436b512294..43eeb7a0ce 100644 --- a/client/src/com/vaadin/client/widgets/Escalator.java +++ b/client/src/com/vaadin/client/widgets/Escalator.java @@ -29,11 +29,13 @@ import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; +import com.google.gwt.animation.client.Animation; 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.JavaScriptObject; +import com.google.gwt.core.client.JsArray; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; @@ -48,6 +50,7 @@ import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableSectionElement; +import com.google.gwt.dom.client.Touch; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.logging.client.LogConfiguration; import com.google.gwt.user.client.Command; @@ -72,6 +75,7 @@ import com.vaadin.client.widget.escalator.PositionFunction.AbsolutePosition; import com.vaadin.client.widget.escalator.PositionFunction.Translate3DPosition; import com.vaadin.client.widget.escalator.PositionFunction.TranslatePosition; import com.vaadin.client.widget.escalator.PositionFunction.WebkitTranslate3DPosition; +import com.vaadin.client.widget.escalator.Row; import com.vaadin.client.widget.escalator.RowContainer; import com.vaadin.client.widget.escalator.RowContainer.BodyRowContainer; import com.vaadin.client.widget.escalator.RowVisibilityChangeEvent; @@ -302,8 +306,6 @@ public class Escalator extends Widget implements RequiresResize, static class JsniUtil { public static class TouchHandlerBundle { - private static final double FLICK_POLL_FREQUENCY = 100d; - /** * A <a href= * "http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsOverlay.html" @@ -338,105 +340,8 @@ public class Escalator extends Widget implements RequiresResize, }-*/; } - private double touches = 0; - private int lastX = 0; - private int lastY = 0; - private boolean snappedScrollEnabled = true; - private double deltaX = 0; - private double deltaY = 0; - private final Escalator escalator; - private CustomTouchEvent latestTouchMoveEvent; - - /** The timestamp of {@link #flickPageX1} and {@link #flickPageY1} */ - private double flickStartTime = 0; - - /** The timestamp of {@link #flickPageX2} and {@link #flickPageY2} */ - private double flickTimestamp = 0; - - /** The most recent flick touch reference Y */ - private double flickPageY1 = -1; - /** The most recent flick touch reference X */ - private double flickPageX1 = -1; - - /** The previous flick touch reference Y, before {@link #flickPageY1} */ - private double flickPageY2 = -1; - /** The previous flick touch reference X, before {@link #flickPageX1} */ - private double flickPageX2 = -1; - - /** - * This animation callback guarantees the fact that we don't scroll - * the grid more than once per visible frame. - * - * It seems that there will never be more touch events than there - * are rendered frames, but there's no guarantee for that. If it was - * guaranteed, we probably could do all of this immediately in - * {@link #touchMove(CustomTouchEvent)}, instead of deferring it - * over here. - */ - private AnimationCallback mover = new AnimationCallback() { - - @Override - public void execute(double timestamp) { - if (touches != 1) { - return; - } - - final int x = latestTouchMoveEvent.getPageX(); - final int y = latestTouchMoveEvent.getPageY(); - - /* - * Check if we need a new flick coordinate sample ( more - * than FLICK_POLL_FREQUENCY ms have passed since the last - * sample ) - */ - if (System.currentTimeMillis() - flickTimestamp > FLICK_POLL_FREQUENCY) { - - flickTimestamp = System.currentTimeMillis(); - // Set target coordinates - flickPageY2 = y; - flickPageX2 = x; - } - - deltaX = x - lastX; - deltaY = y - lastY; - lastX = x; - lastY = y; - - // snap the scroll to the major axes, at first. - if (snappedScrollEnabled) { - final double oldDeltaX = deltaX; - final double oldDeltaY = deltaY; - - /* - * Scrolling snaps to 40 degrees vs. flick scroll's 30 - * degrees, since slow movements have poor resolution - - * it's easy to interpret a slight angle as a steep - * angle, since the sample rate is "unnecessarily" high. - * 40 simply felt better than 30. - */ - final double[] snapped = Escalator.snapDeltas(deltaX, - deltaY, RATIO_OF_40_DEGREES); - deltaX = snapped[0]; - deltaY = snapped[1]; - - /* - * if the snap failed once, let's follow the pointer - * from now on. - */ - if (oldDeltaX != 0 && deltaX == oldDeltaX - && oldDeltaY != 0 && deltaY == oldDeltaY) { - snappedScrollEnabled = false; - } - } - - moveScrollFromEvent(escalator, -deltaX, -deltaY, - latestTouchMoveEvent.getNativeEvent()); - } - }; - private AnimationHandle animationHandle; - public TouchHandlerBundle(final Escalator escalator) { this.escalator = escalator; } @@ -468,79 +373,157 @@ public class Escalator extends Widget implements RequiresResize, }); }-*/; - public void touchStart(final CustomTouchEvent event) { - touches = event.getNativeEvent().getTouches().length(); - if (touches != 1) { - return; + // Duration of the inertial scrolling simulation. Devices with + // larger screens take longer durations. + private static final int DURATION = (int)Window.getClientHeight(); + // multiply scroll velocity with repeated touching + private int acceleration = 1; + private boolean touching = false; + // Two movement objects for storing status and processing touches + private Movement yMov, xMov; + final double MIN_VEL = 0.6, MAX_VEL = 4, F_VEL = 1500, F_ACC = 0.7, F_AXIS = 1; + + // The object to deal with one direction scrolling + private class Movement { + final List<Double> speeds = new ArrayList<Double>(); + final ScrollbarBundle scroll; + double position, offset, velocity, prevPos, prevTime, delta; + boolean run, vertical; + + public Movement(boolean vertical) { + this.vertical = vertical; + scroll = vertical ? escalator.verticalScrollbar : escalator.horizontalScrollbar; } - escalator.scroller.cancelFlickScroll(); + public void startTouch(CustomTouchEvent event) { + speeds.clear(); + prevPos = pagePosition(event); + prevTime = Duration.currentTimeMillis(); + } + public void moveTouch(CustomTouchEvent event) { + double pagePosition = pagePosition(event); + if (pagePosition > -1) { + delta = prevPos - pagePosition; + double now = Duration.currentTimeMillis(); + double ellapsed = now - prevTime; + velocity = delta / ellapsed; + // if last speed was so low, reset speeds and start storing again + if (speeds.size() > 0 && !validSpeed(speeds.get(0))) { + speeds.clear(); + run = true; + } + speeds.add(0, velocity); + prevTime = now; + prevPos = pagePosition; + } + } + public void endTouch(CustomTouchEvent event) { + // Compute average speed + velocity = 0; + for (double s : speeds) { + velocity += s / speeds.size(); + } + position = scroll.getScrollPos(); + // Compute offset, and adjust it with an easing curve so as movement is smoother. + offset = F_VEL * velocity * acceleration * easingInOutCos(velocity, MAX_VEL); + // Check that offset does not over-scroll + double minOff = -scroll.getScrollPos(); + double maxOff = scroll.getScrollSize() - scroll.getOffsetSize() + minOff; + offset = Math.min(Math.max(offset, minOff), maxOff); + // Enable or disable inertia movement in this axis + run = validSpeed(velocity) && minOff < 0 && maxOff > 0; + if (run) { + event.getNativeEvent().preventDefault(); + } + } + void validate(Movement other) { + if (!run || other.velocity > 0 && Math.abs(velocity / other.velocity) < F_AXIS) { + delta = offset = 0; + run = false; + } + } + void stepAnimation(double progress) { + scroll.setScrollPos(position + offset * progress); + } - lastX = event.getPageX(); - lastY = event.getPageY(); + int pagePosition(CustomTouchEvent event) { + JsArray<Touch> a = event.getNativeEvent().getTouches(); + return vertical ? a.get(0).getPageY() : a.get(0).getPageX(); + } + boolean validSpeed(double speed) { + return Math.abs(speed) > MIN_VEL; + } + } - // Reset flick parameters - flickPageX1 = lastX; - flickPageX2 = -1; - flickPageY1 = lastY; - flickPageY2 = -1; - flickStartTime = System.currentTimeMillis(); - flickTimestamp = 0; + // Using GWT animations which take care of native animation frames. + private Animation animation = new Animation() { + public void onUpdate(double progress) { + xMov.stepAnimation(progress); + yMov.stepAnimation(progress); + } + public double interpolate(double progress) { + return easingOutCirc(progress); + }; + public void onComplete() { + touching = false; + escalator.body.domSorter.reschedule(); + }; + public void run(int duration) { + if (xMov.run || yMov.run) { + super.run(duration); + } else { + onComplete(); + } + }; + }; - snappedScrollEnabled = true; + public void touchStart(final CustomTouchEvent event) { + if (event.getNativeEvent().getTouches().length() == 1) { + if (yMov == null) { + yMov = new Movement(true); + xMov = new Movement(false); + } + if (animation.isRunning()) { + acceleration += F_ACC; + event.getNativeEvent().preventDefault(); + animation.cancel(); + } else { + acceleration = 1; + } + xMov.startTouch(event); + yMov.startTouch(event); + touching = true; + } } public void touchMove(final CustomTouchEvent event) { - /* - * since we only use the getPageX/Y, and calculate the diff - * within the handler, we don't need to calculate any - * intermediate deltas. - */ - latestTouchMoveEvent = event; - - if (animationHandle != null) { - animationHandle.cancel(); - } - animationHandle = AnimationScheduler.get() - .requestAnimationFrame(mover, escalator.bodyElem); - event.getNativeEvent().preventDefault(); + xMov.moveTouch(event); + yMov.moveTouch(event); + xMov.validate(yMov); + yMov.validate(xMov); + moveScrollFromEvent(escalator, xMov.delta, yMov.delta, event.getNativeEvent()); } public void touchEnd(final CustomTouchEvent event) { - touches = event.getNativeEvent().getTouches().length(); - - if (touches == 0) { - - /* - * We want to smooth the flick calculations here. We have - * taken a frame of reference every FLICK_POLL_FREQUENCY. - * But if the sample is too fresh, we might introduce noise - * in our sampling, so we use the older sample instead. it - * might be less accurate, but it's smoother. - * - * flickPage?1 is the most recent one, while flickPage?2 is - * the previous one. - */ - - final double finalPageY; - final double finalPageX; - double deltaT = flickTimestamp - flickStartTime; - boolean onlyOneSample = flickPageX2 < 0 || flickPageY2 < 0; - if (onlyOneSample) { - finalPageX = latestTouchMoveEvent.getPageX(); - finalPageY = latestTouchMoveEvent.getPageY(); - } else { - finalPageY = flickPageY2; - finalPageX = flickPageX2; - } - - double deltaX = finalPageX - flickPageX1; - double deltaY = finalPageY - flickPageY1; + xMov.endTouch(event); + yMov.endTouch(event); + xMov.validate(yMov); + yMov.validate(xMov); + // Adjust duration so as longer movements take more duration + boolean vert = !xMov.run || yMov.run && Math.abs(yMov.offset) > Math.abs(xMov.offset); + double delta = Math.abs((vert ? yMov : xMov).offset); + animation.run((int)(3 * DURATION * easingOutExp(delta))); + } - escalator.scroller - .handleFlickScroll(deltaX, deltaY, deltaT); - escalator.body.domSorter.reschedule(); - } + private double easingInOutCos(double val, double max) { + return 0.5 - 0.5 * Math.cos(Math.PI * Math.signum(val) + * Math.min(Math.abs(val), max) / max); + } + private double easingOutExp(double delta) { + return (1 - Math.pow(2, -delta / 1000)); + } + private double easingOutCirc(double progress) { + return Math.sqrt(1 - (progress - 1) * (progress - 1)); } } @@ -570,117 +553,6 @@ public class Escalator extends Widget implements RequiresResize, } } - /** - * The animation callback that handles the animation of a touch-scrolling - * flick with inertia. - */ - private class FlickScrollAnimator implements AnimationCallback { - private static final double MIN_MAGNITUDE = 0.005; - private static final double MAX_SPEED = 7; - - private double velX; - private double velY; - private double prevTime = 0; - private int millisLeft; - private double xFric; - private double yFric; - - private boolean cancelled = false; - private double lastLeft; - private double lastTop; - - /** - * Creates a new animation callback to handle touch-scrolling flick with - * inertia. - * - * @param deltaX - * the last scrolling delta in the x-axis in a touchmove - * @param deltaY - * the last scrolling delta in the y-axis in a touchmove - * @param lastTime - * the timestamp of the last touchmove - */ - public FlickScrollAnimator(final double deltaX, final double deltaY, - final double deltaT) { - velX = Math.max(Math.min(deltaX / deltaT, MAX_SPEED), -MAX_SPEED); - velY = Math.max(Math.min(deltaY / deltaT, MAX_SPEED), -MAX_SPEED); - - lastLeft = horizontalScrollbar.getScrollPos(); - lastTop = verticalScrollbar.getScrollPos(); - - /* - * If we're scrolling mainly in one of the four major directions, - * and only a teeny bit to any other side, snap the scroll to that - * major direction instead. - */ - final double[] snapDeltas = Escalator.snapDeltas(velX, velY, - RATIO_OF_30_DEGREES); - velX = snapDeltas[0]; - velY = snapDeltas[1]; - - if (velX * velX + velY * velY > MIN_MAGNITUDE) { - millisLeft = 1500; - xFric = velX / millisLeft; - yFric = velY / millisLeft; - } else { - millisLeft = 0; - } - - } - - @Override - public void execute(final double doNotUseThisTimestamp) { - /* - * We cannot use the timestamp provided to this method since it is - * of a format that cannot be determined at will. Therefore, we need - * a timestamp format that we can handle, so our calculations are - * correct. - */ - - if (millisLeft <= 0 || cancelled) { - scroller.currentFlickScroller = null; - return; - } - - final double timestamp = Duration.currentTimeMillis(); - if (prevTime == 0) { - prevTime = timestamp; - AnimationScheduler.get().requestAnimationFrame(this); - return; - } - - double currentLeft = horizontalScrollbar.getScrollPos(); - double currentTop = verticalScrollbar.getScrollPos(); - - final double timeDiff = timestamp - prevTime; - double left = currentLeft - velX * timeDiff; - setScrollLeft(left); - velX -= xFric * timeDiff; - - double top = currentTop - velY * timeDiff; - setScrollTop(top); - velY -= yFric * timeDiff; - - cancelBecauseOfEdgeOrCornerMaybe(); - - prevTime = timestamp; - millisLeft -= timeDiff; - lastLeft = currentLeft; - lastTop = currentTop; - AnimationScheduler.get().requestAnimationFrame(this); - } - - private void cancelBecauseOfEdgeOrCornerMaybe() { - if (lastLeft == horizontalScrollbar.getScrollPos() - && lastTop == verticalScrollbar.getScrollPos()) { - cancel(); - } - } - - public void cancel() { - cancelled = true; - } - } /** * ScrollDestination case-specific handling logic. @@ -760,11 +632,6 @@ public class Escalator extends Widget implements RequiresResize, private class Scroller extends JsniWorkaround { private double lastScrollTop = 0; private double lastScrollLeft = 0; - /** - * The current flick scroll animator. This is <code>null</code> if the - * view isn't animating a flick scroll at the moment. - */ - private FlickScrollAnimator currentFlickScroller; public Scroller() { super(Escalator.this); @@ -782,7 +649,7 @@ public class Escalator extends Widget implements RequiresResize, return $entry(function(e) { var target = e.target || e.srcElement; // IE8 uses e.scrElement - + // in case the scroll event was native (i.e. scrollbars were dragged, or // the scrollTop/Left was manually modified), the bundles have old cache // values. We need to make sure that the caches are kept up to date. @@ -1100,30 +967,6 @@ public class Escalator extends Widget implements RequiresResize, } }-*/; - private void cancelFlickScroll() { - if (currentFlickScroller != null) { - currentFlickScroller.cancel(); - } - } - - /** - * Handles a touch-based flick scroll. - * - * @param deltaX - * the last scrolling delta in the x-axis in a touchmove - * @param deltaY - * the last scrolling delta in the y-axis in a touchmove - * @param lastTime - * the timestamp of the last touchmove - */ - public void handleFlickScroll(double deltaX, double deltaY, - double deltaT) { - currentFlickScroller = new FlickScrollAnimator(deltaX, deltaY, - deltaT); - AnimationScheduler.get() - .requestAnimationFrame(currentFlickScroller); - } - public void scrollToColumn(final int columnIndex, final ScrollDestination destination, final int padding) { assert columnIndex >= columnConfiguration.frozenColumns : "Can't scroll to a frozen column"; @@ -1475,6 +1318,9 @@ public class Escalator extends Widget implements RequiresResize, cellElem.addClassName("frozen"); position.set(cellElem, scroller.lastScrollLeft, 0); } + if (columnConfiguration.frozenColumns > 0 && col == columnConfiguration.frozenColumns - 1) { + cellElem.addClassName("last-frozen"); + } } referenceRow = paintInsertRow(referenceRow, tr, row); @@ -1732,6 +1578,14 @@ public class Escalator extends Widget implements RequiresResize, } public void setColumnFrozen(int column, boolean frozen) { + toggleFrozenColumnClass(column, frozen, "frozen"); + + if (frozen) { + updateFreezePosition(column, scroller.lastScrollLeft); + } + } + + private void toggleFrozenColumnClass(int column, boolean frozen, String className) { final NodeList<TableRowElement> childRows = root.getRows(); for (int row = 0; row < childRows.getLength(); row++) { @@ -1742,16 +1596,16 @@ public class Escalator extends Widget implements RequiresResize, TableCellElement cell = tr.getCells().getItem(column); if (frozen) { - cell.addClassName("frozen"); + cell.addClassName(className); } else { - cell.removeClassName("frozen"); + cell.removeClassName(className); position.reset(cell); } } + } - if (frozen) { - updateFreezePosition(column, scroller.lastScrollLeft); - } + public void setColumnLastFrozen(int column, boolean lastFrozen) { + toggleFrozenColumnClass(column, lastFrozen, "last-frozen"); } public void updateFreezePosition(int column, double scrollLeft) { @@ -2470,9 +2324,9 @@ public class Escalator extends Widget implements RequiresResize, private boolean sortIfConditionsMet() { boolean enoughFramesHavePassed = framesPassed >= REQUIRED_FRAMES_PASSED; boolean enoughTimeHasPassed = (Duration.currentTimeMillis() - startTime) >= SORT_DELAY_MILLIS; - boolean notAnimatingFlick = (scroller.currentFlickScroller == null); + boolean notTouchActivity = !scroller.touchHandlerBundle.touching; boolean conditionsMet = enoughFramesHavePassed - && enoughTimeHasPassed && notAnimatingFlick; + && enoughTimeHasPassed && notTouchActivity; if (conditionsMet) { resetConditions(); @@ -2652,15 +2506,7 @@ public class Escalator extends Widget implements RequiresResize, if (rowsWereMoved) { fireRowVisibilityChangeEvent(); - - if (scroller.touchHandlerBundle.touches == 0) { - /* - * this will never be called on touch scrolling. That is - * handled separately and explicitly by - * TouchHandlerBundle.touchEnd(); - */ - domSorter.reschedule(); - } + domSorter.reschedule(); } } @@ -2756,17 +2602,33 @@ public class Escalator extends Widget implements RequiresResize, // move the surrounding rows to their correct places. double rowTop = (unupdatedLogicalStart + (end - start)) * getDefaultRowHeight(); - final ListIterator<TableRowElement> i = visualRowOrder - .listIterator(visualTargetIndex + (end - start)); - - int logicalRowIndexCursor = unupdatedLogicalStart; - while (i.hasNext()) { - rowTop += spacerContainer - .getSpacerHeight(logicalRowIndexCursor++); - final TableRowElement tr = i.next(); - setRowPosition(tr, 0, rowTop); - rowTop += getDefaultRowHeight(); + // TODO: Get rid of this try/catch block by fixing the + // underlying issue. The reason for this erroneous behavior + // might be that Escalator actually works 'by mistake', and + // the order of operations is, in fact, wrong. + try { + final ListIterator<TableRowElement> i = visualRowOrder + .listIterator(visualTargetIndex + (end - start)); + + int logicalRowIndexCursor = unupdatedLogicalStart; + while (i.hasNext()) { + rowTop += spacerContainer + .getSpacerHeight(logicalRowIndexCursor++); + + final TableRowElement tr = i.next(); + setRowPosition(tr, 0, rowTop); + rowTop += getDefaultRowHeight(); + } + } catch (Exception e) { + Logger logger = getLogger(); + logger.warning("Ignored out-of-bounds row element access"); + logger.warning("Escalator state: start=" + start + + ", end=" + end + ", visualTargetIndex=" + + visualTargetIndex + + ", visualRowOrder.size()=" + + visualRowOrder.size()); + logger.warning(e.toString()); } } @@ -4309,6 +4171,17 @@ public class Escalator extends Widget implements RequiresResize, firstUnaffectedCol = oldCount; } + if (oldCount > 0) { + header.setColumnLastFrozen(oldCount - 1, false); + body.setColumnLastFrozen(oldCount - 1, false); + footer.setColumnLastFrozen(oldCount - 1, false); + } + if (count > 0) { + header.setColumnLastFrozen(count - 1, true); + body.setColumnLastFrozen(count - 1, true); + footer.setColumnLastFrozen(count - 1, true); + } + for (int col = firstAffectedCol; col < firstUnaffectedCol; col++) { header.setColumnFrozen(col, frozen); body.setColumnFrozen(col, frozen); @@ -5619,14 +5492,29 @@ public class Escalator extends Widget implements RequiresResize, horizontalScrollbar.setScrollbarThickness(scrollbarThickness); horizontalScrollbar .addVisibilityHandler(new ScrollbarBundle.VisibilityHandler() { + + private boolean queued = false; + @Override public void visibilityChanged( ScrollbarBundle.VisibilityChangeEvent event) { + if (queued) { + return; + } + queued = true; + /* * We either lost or gained a scrollbar. In any case, we * need to change the height, if it's defined by rows. */ - applyHeightByRows(); + Scheduler.get().scheduleFinally(new ScheduledCommand() { + + @Override + public void execute() { + applyHeightByRows(); + queued = false; + } + }); } }); @@ -6022,10 +5910,14 @@ public class Escalator extends Widget implements RequiresResize, public void scrollToRow(final int rowIndex, final ScrollDestination destination, final int padding) throws IndexOutOfBoundsException, IllegalArgumentException { - validateScrollDestination(destination, padding); - verifyValidRowIndex(rowIndex); - - scroller.scrollToRow(rowIndex, destination, padding); + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + validateScrollDestination(destination, padding); + verifyValidRowIndex(rowIndex); + scroller.scrollToRow(rowIndex, destination, padding); + } + }); } private void verifyValidRowIndex(final int rowIndex) { @@ -6086,55 +5978,62 @@ public class Escalator extends Widget implements RequiresResize, * {@code destination == null}; or if {@code rowIndex == -1} and * there is no spacer open at that index. */ - public void scrollToRowAndSpacer(int rowIndex, - ScrollDestination destination, int padding) + public void scrollToRowAndSpacer(final int rowIndex, + final ScrollDestination destination, final int padding) throws IllegalArgumentException { - validateScrollDestination(destination, padding); - if (rowIndex != -1) { - verifyValidRowIndex(rowIndex); - } + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + validateScrollDestination(destination, padding); + if (rowIndex != -1) { + verifyValidRowIndex(rowIndex); + } - // row range - final Range rowRange; - if (rowIndex != -1) { - int rowTop = (int) Math.floor(body.getRowTop(rowIndex)); - int rowHeight = (int) Math.ceil(body.getDefaultRowHeight()); - rowRange = Range.withLength(rowTop, rowHeight); - } else { - rowRange = Range.withLength(0, 0); - } + // row range + final Range rowRange; + if (rowIndex != -1) { + int rowTop = (int) Math.floor(body.getRowTop(rowIndex)); + int rowHeight = (int) Math.ceil(body.getDefaultRowHeight()); + rowRange = Range.withLength(rowTop, rowHeight); + } else { + rowRange = Range.withLength(0, 0); + } - // get spacer - final SpacerContainer.SpacerImpl spacer = body.spacerContainer - .getSpacer(rowIndex); + // get spacer + final SpacerContainer.SpacerImpl spacer = body.spacerContainer + .getSpacer(rowIndex); - if (rowIndex == -1 && spacer == null) { - throw new IllegalArgumentException("Cannot scroll to row index " - + "-1, as there is no spacer open at that index."); - } + if (rowIndex == -1 && spacer == null) { + throw new IllegalArgumentException( + "Cannot scroll to row index " + + "-1, as there is no spacer open at that index."); + } - // make into target range - final Range targetRange; - if (spacer != null) { - final int spacerTop = (int) Math.floor(spacer.getTop()); - final int spacerHeight = (int) Math.ceil(spacer.getHeight()); - Range spacerRange = Range.withLength(spacerTop, spacerHeight); + // make into target range + final Range targetRange; + if (spacer != null) { + final int spacerTop = (int) Math.floor(spacer.getTop()); + final int spacerHeight = (int) Math.ceil(spacer.getHeight()); + Range spacerRange = Range.withLength(spacerTop, + spacerHeight); - targetRange = rowRange.combineWith(spacerRange); - } else { - targetRange = rowRange; - } + targetRange = rowRange.combineWith(spacerRange); + } else { + targetRange = rowRange; + } - // get params - int targetStart = targetRange.getStart(); - int targetEnd = targetRange.getEnd(); - double viewportStart = getScrollTop(); - double viewportEnd = viewportStart + body.getHeightOfSection(); + // get params + int targetStart = targetRange.getStart(); + int targetEnd = targetRange.getEnd(); + double viewportStart = getScrollTop(); + double viewportEnd = viewportStart + body.getHeightOfSection(); - double scrollPos = getScrollPos(destination, targetStart, targetEnd, - viewportStart, viewportEnd, padding); + double scrollPos = getScrollPos(destination, targetStart, + targetEnd, viewportStart, viewportEnd, padding); - setScrollTop(scrollPos); + setScrollTop(scrollPos); + } + }); } private static void validateScrollDestination( diff --git a/client/src/com/vaadin/client/widgets/Grid.java b/client/src/com/vaadin/client/widgets/Grid.java index 58fc532a77..fc8151272d 100644 --- a/client/src/com/vaadin/client/widgets/Grid.java +++ b/client/src/com/vaadin/client/widgets/Grid.java @@ -31,7 +31,6 @@ import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; -import com.google.gwt.core.client.Duration; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.core.shared.GWT; @@ -42,6 +41,7 @@ import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.dom.client.TableRowElement; @@ -49,6 +49,9 @@ import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.HasFocusHandlers; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; @@ -79,6 +82,7 @@ import com.vaadin.client.Focusable; import com.vaadin.client.WidgetUtil; import com.vaadin.client.data.DataChangeHandler; import com.vaadin.client.data.DataSource; +import com.vaadin.client.data.DataSource.RowHandle; import com.vaadin.client.renderers.ComplexRenderer; import com.vaadin.client.renderers.Renderer; import com.vaadin.client.renderers.WidgetRenderer; @@ -103,6 +107,7 @@ import com.vaadin.client.widget.grid.CellReference; import com.vaadin.client.widget.grid.CellStyleGenerator; import com.vaadin.client.widget.grid.DataAvailableEvent; import com.vaadin.client.widget.grid.DataAvailableHandler; +import com.vaadin.client.widget.grid.DefaultEditorEventHandler; import com.vaadin.client.widget.grid.DetailsGenerator; import com.vaadin.client.widget.grid.EditorHandler; import com.vaadin.client.widget.grid.EditorHandler.EditorRequest; @@ -121,6 +126,9 @@ import com.vaadin.client.widget.grid.events.ColumnReorderEvent; import com.vaadin.client.widget.grid.events.ColumnReorderHandler; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeEvent; import com.vaadin.client.widget.grid.events.ColumnVisibilityChangeHandler; +import com.vaadin.client.widget.grid.events.EditorCloseEvent; +import com.vaadin.client.widget.grid.events.EditorEvent; +import com.vaadin.client.widget.grid.events.EditorEventHandler; import com.vaadin.client.widget.grid.events.FooterClickHandler; import com.vaadin.client.widget.grid.events.FooterDoubleClickHandler; import com.vaadin.client.widget.grid.events.FooterKeyDownHandler; @@ -206,8 +214,10 @@ import com.vaadin.shared.util.SharedUtil; * @author Vaadin Ltd */ public class Grid<T> extends ResizeComposite implements - HasSelectionHandlers<T>, SubPartAware, DeferredWorker, HasWidgets, - HasEnabled { + HasSelectionHandlers<T>, SubPartAware, DeferredWorker, Focusable, + com.google.gwt.user.client.ui.Focusable, HasWidgets, HasEnabled { + + private static final String SELECT_ALL_CHECKBOX_CLASSNAME = "-select-all-checkbox"; /** * Enum describing different sections of Grid. @@ -1081,14 +1091,10 @@ public class Grid<T> extends ResizeComposite implements } completed = true; - grid.getEditor().setErrorMessage(errorMessage); - - grid.getEditor().clearEditorColumnErrors(); - if (errorColumns != null) { - for (Column<?, T> column : errorColumns) { - grid.getEditor().setEditorColumnError(column, true); - } + if (errorColumns == null) { + errorColumns = Collections.emptySet(); } + grid.getEditor().setEditorError(errorMessage, errorColumns); } @Override @@ -1115,10 +1121,104 @@ public class Grid<T> extends ResizeComposite implements } /** + * A wrapper for native DOM events originating from Grid. In addition to the + * native event, contains a {@link CellReference} instance specifying which + * cell the event originated from. + * + * @since + * @param <T> + * The row type of the grid + */ + public static class GridEvent<T> { + private Event event; + private EventCellReference<T> cell; + + protected GridEvent(Event event, EventCellReference<T> cell) { + this.event = event; + this.cell = cell; + } + + /** + * Returns the wrapped DOM event. + * + * @return the DOM event + */ + public Event getDomEvent() { + return event; + } + + /** + * Returns the Grid cell this event originated from. + * + * @return the event cell + */ + public EventCellReference<T> getCell() { + return cell; + } + + /** + * Returns the Grid instance this event originated from. + * + * @return the grid + */ + public Grid<T> getGrid() { + return cell.getGrid(); + } + } + + /** + * A wrapper for native DOM events related to the {@link Editor Grid editor} + * . + * + * @since + * @param <T> + * the row type of the grid + */ + public static class EditorDomEvent<T> extends GridEvent<T> { + + protected EditorDomEvent(Event event, EventCellReference<T> cell) { + super(event, cell); + } + + /** + * Returns the editor of the Grid this event originated from. + * + * @return the related editor instance + */ + public Editor<T> getEditor() { + return getGrid().getEditor(); + } + + /** + * Returns the row index the editor is open at. If the editor is not + * open, returns -1. + * + * @return the index of the edited row or -1 if editor is not open + */ + public int getRowIndex() { + return getEditor().rowIndex; + } + + /** + * Returns the column index the editor was opened at. If the editor is + * not open, returns -1. + * + * @return the column index or -1 if editor is not open + */ + public int getFocusedColumnIndex() { + return getEditor().focusedColumnIndex; + } + } + + /** * An editor UI for Grid rows. A single Grid row at a time can be opened for * editing. + * + * @since + * @param <T> + * the row type of the grid */ - protected static class Editor<T> { + public static class Editor<T> { public static final int KEYCODE_SHOW = KeyCodes.KEY_ENTER; public static final int KEYCODE_HIDE = KeyCodes.KEY_ESCAPE; @@ -1126,15 +1226,41 @@ public class Grid<T> extends ResizeComposite implements private static final String ERROR_CLASS_NAME = "error"; private static final String NOT_EDITABLE_CLASS_NAME = "not-editable"; + /** + * A handler for events related to the Grid editor. Responsible for + * opening, moving or closing the editor based on the received event. + * + * @since + * @author Vaadin Ltd + * @param <T> + * the row type of the grid + */ + public interface EventHandler<T> { + /** + * Handles editor-related events in an appropriate way. Opens, + * moves, or closes the editor based on the given event. + * + * @param event + * the received event + * @return true if the event was handled and nothing else should be + * done, false otherwise + */ + boolean handleEvent(EditorDomEvent<T> event); + } + protected enum State { INACTIVE, ACTIVATING, BINDING, ACTIVE, SAVING } private Grid<T> grid; private EditorHandler<T> handler; + private EventHandler<T> eventHandler = GWT + .create(DefaultEditorEventHandler.class); private DivElement editorOverlay = DivElement.as(DOM.createDiv()); private DivElement cellWrapper = DivElement.as(DOM.createDiv()); + private DivElement frozenCellWrapper = DivElement.as(DOM.createDiv()); + private DivElement messageAndButtonsWrapper = DivElement.as(DOM .createDiv()); @@ -1146,15 +1272,25 @@ public class Grid<T> extends ResizeComposite implements private DivElement message = DivElement.as(DOM.createDiv()); private Map<Column<?, T>, Widget> columnToWidget = new HashMap<Column<?, T>, Widget>(); + private List<HandlerRegistration> focusHandlers = new ArrayList<HandlerRegistration>(); private boolean enabled = false; private State state = State.INACTIVE; private int rowIndex = -1; - private int columnIndex = -1; + private int focusedColumnIndex = -1; private String styleName = null; + /* + * Used to track Grid horizontal scrolling + */ private HandlerRegistration scrollHandler; + /* + * Used to open editor once Grid has vertically scrolled to the proper + * position and data is available + */ + private HandlerRegistration dataAvailableHandler; + private final Button saveButton; private final Button cancelButton; @@ -1209,6 +1345,7 @@ public class Grid<T> extends ResizeComposite implements + " remember to call success() or fail()?"); } }; + private final EditorRequestImpl.RequestCallback<T> bindRequestCallback = new EditorRequestImpl.RequestCallback<T>() { @Override public void onSuccess(EditorRequest<T> request) { @@ -1216,10 +1353,7 @@ public class Grid<T> extends ResizeComposite implements state = State.ACTIVE; bindTimeout.cancel(); - assert rowIndex == request.getRowIndex() : "Request row index " - + request.getRowIndex() - + " did not match the saved row index " + rowIndex; - + rowIndex = request.getRowIndex(); showOverlay(); } } @@ -1227,22 +1361,30 @@ public class Grid<T> extends ResizeComposite implements @Override public void onError(EditorRequest<T> request) { if (state == State.BINDING) { - state = State.INACTIVE; + if (rowIndex == -1) { + doCancel(); + } else { + state = State.ACTIVE; + } bindTimeout.cancel(); // TODO show something in the DOM as well? getLogger().warning( "An error occurred while trying to show the " + "Grid editor"); - grid.getEscalator().setScrollLocked(Direction.VERTICAL, - false); - updateSelectionCheckboxesAsNeeded(true); } } }; /** A set of all the columns that display an error flag. */ private final Set<Column<?, T>> columnErrors = new HashSet<Grid.Column<?, T>>(); + private boolean buffered = true; + + /** Original position of editor */ + private double originalTop; + /** Original scroll position of grid when editor was opened */ + private double originalScrollTop; + private RowHandle<T> pinnedRowHandle; public Editor() { saveButton = new Button(); @@ -1264,7 +1406,9 @@ public class Grid<T> extends ResizeComposite implements }); } - public void setErrorMessage(String errorMessage) { + public void setEditorError(String errorMessage, + Collection<Column<?, T>> errorColumns) { + if (errorMessage == null) { message.removeFromParent(); } else { @@ -1273,6 +1417,17 @@ public class Grid<T> extends ResizeComposite implements messageWrapper.appendChild(message); } } + // In unbuffered mode only show message wrapper if there is an error + if (!isBuffered()) { + setMessageAndButtonsWrapperVisible(errorMessage != null); + } + + if (state == State.ACTIVE || state == State.SAVING) { + for (Column<?, T> c : grid.getColumns()) { + grid.getEditor().setEditorColumnError(c, + errorColumns.contains(c)); + } + } } public int getRow() { @@ -1280,12 +1435,26 @@ public class Grid<T> extends ResizeComposite implements } /** - * Equivalent to {@code editRow(rowIndex, -1)}. + * If a cell of this Grid had focus once this editRow call was + * triggered, the editor component at the previously focused column + * index will be focused. + * + * If a Grid cell was not focused prior to calling this method, it will + * be equivalent to {@code editRow(rowIndex, -1)}. * * @see #editRow(int, int) */ public void editRow(int rowIndex) { - editRow(rowIndex, -1); + // Focus the last focused column in the editor iff grid or its child + // was focused before the edit request + Cell focusedCell = grid.cellFocusHandler.getFocusedCell(); + Element focusedElement = WidgetUtil.getFocusedElement(); + if (focusedCell != null && focusedElement != null + && grid.getElement().isOrHasChild(focusedElement)) { + editRow(rowIndex, focusedCell.getColumn()); + } else { + editRow(rowIndex, -1); + } } /** @@ -1302,28 +1471,41 @@ public class Grid<T> extends ResizeComposite implements * @throws IllegalStateException * if this editor is not enabled * @throws IllegalStateException - * if this editor is already in edit mode + * if this editor is already in edit mode and in buffered + * mode * * @since 7.5 */ - public void editRow(int rowIndex, int columnIndex) { + public void editRow(final int rowIndex, int columnIndex) { if (!enabled) { throw new IllegalStateException( "Cannot edit row: editor is not enabled"); } if (state != State.INACTIVE) { - throw new IllegalStateException( - "Cannot edit row: editor already in edit mode"); + if (isBuffered()) { + throw new IllegalStateException( + "Cannot edit row: editor already in edit mode"); + } } this.rowIndex = rowIndex; - this.columnIndex = columnIndex; - + this.focusedColumnIndex = columnIndex; state = State.ACTIVATING; if (grid.getEscalator().getVisibleRowRange().contains(rowIndex)) { - show(); + show(rowIndex); } else { + hideOverlay(); + dataAvailableHandler = grid + .addDataAvailableHandler(new DataAvailableHandler() { + @Override + public void onDataAvailable(DataAvailableEvent event) { + if (event.getAvailableRows().contains(rowIndex)) { + show(rowIndex); + dataAvailableHandler.removeHandler(); + } + } + }); grid.scrollToRow(rowIndex, ScrollDestination.MIDDLE); } } @@ -1346,14 +1528,18 @@ public class Grid<T> extends ResizeComposite implements throw new IllegalStateException( "Cannot cancel edit: editor is not in edit mode"); } - hideOverlay(); - grid.getEscalator().setScrollLocked(Direction.VERTICAL, false); + handler.cancel(new EditorRequestImpl<T>(grid, rowIndex, null)); + doCancel(); + } - EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex, - null); - handler.cancel(request); + private void doCancel() { + hideOverlay(); state = State.INACTIVE; + rowIndex = -1; + focusedColumnIndex = -1; + grid.getEscalator().setScrollLocked(Direction.VERTICAL, false); updateSelectionCheckboxesAsNeeded(true); + grid.fireEvent(new EditorCloseEvent(grid.eventCell)); } private void updateSelectionCheckboxesAsNeeded(boolean isEnabled) { @@ -1446,14 +1632,15 @@ public class Grid<T> extends ResizeComposite implements this.enabled = enabled; } - protected void show() { + protected void show(int rowIndex) { if (state == State.ACTIVATING) { state = State.BINDING; bindTimeout.schedule(BIND_TIMEOUT_MS); EditorRequest<T> request = new EditorRequestImpl<T>(grid, rowIndex, bindRequestCallback); handler.bind(request); - grid.getEscalator().setScrollLocked(Direction.VERTICAL, true); + grid.getEscalator().setScrollLocked(Direction.VERTICAL, + isBuffered()); updateSelectionCheckboxesAsNeeded(false); } } @@ -1463,15 +1650,6 @@ public class Grid<T> extends ResizeComposite implements assert this.grid == null : "Can only attach editor to Grid once"; this.grid = grid; - - grid.addDataAvailableHandler(new DataAvailableHandler() { - @Override - public void onDataAvailable(DataAvailableEvent event) { - if (event.getAvailableRows().contains(rowIndex)) { - show(); - } - } - }); } protected State getState() { @@ -1516,6 +1694,8 @@ public class Grid<T> extends ResizeComposite implements * @since 7.5 */ protected void showOverlay() { + // Ensure overlay is hidden initially + hideOverlay(); DivElement gridElement = DivElement.as(grid.getElement()); @@ -1526,19 +1706,38 @@ public class Grid<T> extends ResizeComposite implements @Override public void onScroll(ScrollEvent event) { updateHorizontalScrollPosition(); + if (!isBuffered()) { + updateVerticalScrollPosition(); + } } }); gridElement.appendChild(editorOverlay); + editorOverlay.appendChild(frozenCellWrapper); editorOverlay.appendChild(cellWrapper); editorOverlay.appendChild(messageAndButtonsWrapper); + int frozenColumns = grid.getVisibleFrozenColumnCount(); + double frozenColumnsWidth = 0; + double cellHeight = 0; + for (int i = 0; i < tr.getCells().getLength(); i++) { Element cell = createCell(tr.getCells().getItem(i)); - - cellWrapper.appendChild(cell); + cellHeight = Math.max(cellHeight, WidgetUtil + .getRequiredHeightBoundingClientRectDouble(tr + .getCells().getItem(i))); Column<?, T> column = grid.getVisibleColumn(i); + + if (i < frozenColumns) { + frozenCellWrapper.appendChild(cell); + frozenColumnsWidth += WidgetUtil + .getRequiredWidthBoundingClientRectDouble(tr + .getCells().getItem(i)); + } else { + cellWrapper.appendChild(cell); + } + if (column.isEditable()) { Widget editor = getHandler().getWidget(column); @@ -1547,7 +1746,28 @@ public class Grid<T> extends ResizeComposite implements attachWidget(editor, cell); } - if (i == columnIndex) { + final int currentColumnIndex = i; + if (editor instanceof HasFocusHandlers) { + // Use a proper focus handler if available + focusHandlers.add(((HasFocusHandlers) editor) + .addFocusHandler(new FocusHandler() { + @Override + public void onFocus(FocusEvent event) { + focusedColumnIndex = currentColumnIndex; + } + })); + } else { + // Try sniffing for DOM focus events + focusHandlers.add(editor.addDomHandler( + new FocusHandler() { + @Override + public void onFocus(FocusEvent event) { + focusedColumnIndex = currentColumnIndex; + } + }, FocusEvent.getType())); + } + + if (i == focusedColumnIndex) { if (editor instanceof Focusable) { ((Focusable) editor).focus(); } else if (editor instanceof com.google.gwt.user.client.ui.Focusable) { @@ -1557,17 +1777,70 @@ public class Grid<T> extends ResizeComposite implements } } else { cell.addClassName(NOT_EDITABLE_CLASS_NAME); + cell.addClassName(tr.getCells().getItem(i).getClassName()); + // If the focused or frozen stylename is present it should + // not be inherited by the editor cell as it is not useful + // in the editor and would look broken without additional + // style rules. This is a bit of a hack. + cell.removeClassName(grid.cellFocusStyleName); + cell.removeClassName("frozen"); + + if (column == grid.selectionColumn) { + // Duplicate selection column CheckBox + + pinnedRowHandle = grid.getDataSource().getHandle( + grid.getDataSource().getRow(rowIndex)); + pinnedRowHandle.pin(); + + // We need to duplicate the selection CheckBox for the + // editor overlay since the original one is hidden by + // the overlay + final CheckBox checkBox = GWT.create(CheckBox.class); + checkBox.setValue(grid.isSelected(pinnedRowHandle + .getRow())); + checkBox.sinkEvents(Event.ONCLICK); + + checkBox.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + T row = pinnedRowHandle.getRow(); + if (grid.isSelected(row)) { + grid.deselect(row); + } else { + grid.select(row); + } + } + }); + attachWidget(checkBox, cell); + columnToWidget.put(column, checkBox); + + // Only enable CheckBox in non-buffered mode + checkBox.setEnabled(!isBuffered()); + + } else if (!(column.getRenderer() instanceof WidgetRenderer)) { + // Copy non-widget content directly + cell.setInnerHTML(tr.getCells().getItem(i) + .getInnerHTML()); + } } } + setBounds(frozenCellWrapper, 0, 0, frozenColumnsWidth, 0); + setBounds(cellWrapper, frozenColumnsWidth, 0, tr.getOffsetWidth() + - frozenColumnsWidth, cellHeight); + // Only add these elements once if (!messageAndButtonsWrapper.isOrHasChild(messageWrapper)) { messageAndButtonsWrapper.appendChild(messageWrapper); messageAndButtonsWrapper.appendChild(buttonsWrapper); } - attachWidget(saveButton, buttonsWrapper); - attachWidget(cancelButton, buttonsWrapper); + if (isBuffered()) { + attachWidget(saveButton, buttonsWrapper); + attachWidget(cancelButton, buttonsWrapper); + } + + setMessageAndButtonsWrapperVisible(isBuffered()); updateHorizontalScrollPosition(); @@ -1579,9 +1852,11 @@ public class Grid<T> extends ResizeComposite implements int gridTop = gridElement.getAbsoluteTop(); double overlayTop = rowTop + bodyTop - gridTop; - if (buttonsShouldBeRenderedBelow(tr)) { + originalScrollTop = grid.getScrollTop(); + if (!isBuffered() || buttonsShouldBeRenderedBelow(tr)) { // Default case, editor buttons are below the edited row editorOverlay.getStyle().setTop(overlayTop, Unit.PX); + originalTop = overlayTop; editorOverlay.getStyle().clearBottom(); } else { // Move message and buttons wrapper on top of cell wrapper if @@ -1614,6 +1889,20 @@ public class Grid<T> extends ResizeComposite implements } protected void hideOverlay() { + if (editorOverlay.getParentElement() == null) { + return; + } + + if (pinnedRowHandle != null) { + pinnedRowHandle.unpin(); + pinnedRowHandle = null; + } + + for (HandlerRegistration r : focusHandlers) { + r.removeHandler(); + } + focusHandlers.clear(); + for (Widget w : columnToWidget.values()) { setParent(w, null); } @@ -1624,11 +1913,16 @@ public class Grid<T> extends ResizeComposite implements editorOverlay.removeAllChildren(); cellWrapper.removeAllChildren(); + frozenCellWrapper.removeAllChildren(); editorOverlay.removeFromParent(); scrollHandler.removeHandler(); clearEditorColumnErrors(); + + if (focusedColumnIndex != -1) { + grid.focusCell(rowIndex, focusedColumnIndex); + } } protected void setStylePrimaryName(String primaryName) { @@ -1636,6 +1930,7 @@ public class Grid<T> extends ResizeComposite implements editorOverlay.removeClassName(styleName); cellWrapper.removeClassName(styleName + "-cells"); + frozenCellWrapper.removeClassName(styleName + "-cells"); messageAndButtonsWrapper.removeClassName(styleName + "-footer"); messageWrapper.removeClassName(styleName + "-message"); @@ -1648,6 +1943,7 @@ public class Grid<T> extends ResizeComposite implements editorOverlay.setClassName(styleName); cellWrapper.setClassName(styleName + "-cells"); + frozenCellWrapper.setClassName(styleName + "-cells frozen"); messageAndButtonsWrapper.setClassName(styleName + "-footer"); messageWrapper.setClassName(styleName + "-message"); @@ -1698,7 +1994,37 @@ public class Grid<T> extends ResizeComposite implements private void updateHorizontalScrollPosition() { double scrollLeft = grid.getScrollLeft(); - cellWrapper.getStyle().setLeft(-scrollLeft, Unit.PX); + cellWrapper.getStyle().setLeft( + frozenCellWrapper.getOffsetWidth() - scrollLeft, Unit.PX); + } + + /** + * Moves the editor overlay on scroll so that it stays on top of the + * edited row. This will also snap the editor to top or bottom of the + * row container if the edited row is scrolled out of the visible area. + */ + private void updateVerticalScrollPosition() { + double newScrollTop = grid.getScrollTop(); + + int gridTop = grid.getElement().getAbsoluteTop(); + int editorHeight = editorOverlay.getOffsetHeight(); + + Escalator escalator = grid.getEscalator(); + TableSectionElement header = escalator.getHeader().getElement(); + int footerTop = escalator.getFooter().getElement().getAbsoluteTop(); + int headerBottom = header.getAbsoluteBottom(); + + double newTop = originalTop - (newScrollTop - originalScrollTop); + + if (newTop + gridTop < headerBottom) { + // Snap editor to top of the row container + newTop = header.getOffsetHeight(); + } else if (newTop + gridTop > footerTop - editorHeight) { + // Snap editor to the bottom of the row container + newTop = footerTop - editorHeight - gridTop; + } + + editorOverlay.getStyle().setTop(newTop, Unit.PX); } protected void setGridEnabled(boolean enabled) { @@ -1777,6 +2103,44 @@ public class Grid<T> extends ResizeComposite implements public boolean isEditorColumnError(Column<?, T> column) { return columnErrors.contains(column); } + + public void setBuffered(boolean buffered) { + this.buffered = buffered; + setMessageAndButtonsWrapperVisible(buffered); + } + + public boolean isBuffered() { + return buffered; + } + + private void setMessageAndButtonsWrapperVisible(boolean visible) { + if (visible) { + messageAndButtonsWrapper.getStyle().clearDisplay(); + } else { + messageAndButtonsWrapper.getStyle().setDisplay(Display.NONE); + } + } + + /** + * Sets the event handler for this Editor. + * + * @since + * @param handler + * the new event handler + */ + public void setEventHandler(EventHandler<T> handler) { + eventHandler = handler; + } + + /** + * Returns the event handler of this Editor. + * + * @since + * @return the current event handler + */ + public EventHandler<T> getEventHandler() { + return eventHandler; + } } public static abstract class AbstractGridKeyEvent<HANDLER extends AbstractGridKeyEventHandler> @@ -2351,6 +2715,7 @@ public class Grid<T> extends ResizeComposite implements private boolean initDone = false; private boolean selected = false; + private CheckBox selectAllCheckBox; SelectionColumn(final Renderer<Boolean> selectColumnRenderer) { super(selectColumnRenderer); @@ -2375,41 +2740,57 @@ public class Grid<T> extends ResizeComposite implements * exist. */ final SelectionModel.Multi<T> model = (Multi<T>) getSelectionModel(); - final CheckBox checkBox = GWT.create(CheckBox.class); - checkBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() { - @Override - public void onValueChange(ValueChangeEvent<Boolean> event) { - if (event.getValue()) { - fireEvent(new SelectAllEvent<T>(model)); - selected = true; - } else { - model.deselectAll(); - selected = false; - } - } - }); - checkBox.setValue(selected); - selectionCell.setWidget(checkBox); - // Select all with space when "select all" cell is active - addHeaderKeyUpHandler(new HeaderKeyUpHandler() { - @Override - public void onKeyUp(GridKeyUpEvent event) { - if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { - return; - } - HeaderRow targetHeaderRow = getHeader().getRow( - event.getFocusedCell().getRowIndex()); - if (!targetHeaderRow.isDefault()) { - return; + if (selectAllCheckBox == null) { + selectAllCheckBox = GWT.create(CheckBox.class); + selectAllCheckBox.setStylePrimaryName(getStylePrimaryName() + + SELECT_ALL_CHECKBOX_CLASSNAME); + selectAllCheckBox + .addValueChangeHandler(new ValueChangeHandler<Boolean>() { + + @Override + public void onValueChange( + ValueChangeEvent<Boolean> event) { + if (event.getValue()) { + fireEvent(new SelectAllEvent<T>(model)); + selected = true; + } else { + model.deselectAll(); + selected = false; + } + } + }); + selectAllCheckBox.setValue(selected); + + // Select all with space when "select all" cell is active + addHeaderKeyUpHandler(new HeaderKeyUpHandler() { + @Override + public void onKeyUp(GridKeyUpEvent event) { + if (event.getNativeKeyCode() != KeyCodes.KEY_SPACE) { + return; + } + HeaderRow targetHeaderRow = getHeader().getRow( + event.getFocusedCell().getRowIndex()); + if (!targetHeaderRow.isDefault()) { + return; + } + if (event.getFocusedCell().getColumn() == SelectionColumn.this) { + // Send events to ensure state is updated + selectAllCheckBox.setValue( + !selectAllCheckBox.getValue(), true); + } } - if (event.getFocusedCell().getColumn() == SelectionColumn.this) { - // Send events to ensure row selection state is - // updated - checkBox.setValue(!checkBox.getValue(), true); + }); + } else { + for (HeaderRow row : header.getRows()) { + if (row.getCell(this).getType() == GridStaticCellType.WIDGET) { + // Detach from old header. + row.getCell(this).setText(""); } } - }); + } + + selectionCell.setWidget(selectAllCheckBox); } @Override @@ -2471,7 +2852,6 @@ public class Grid<T> extends ResizeComposite implements super.setEditable(editable); return this; } - } /** @@ -2736,7 +3116,9 @@ public class Grid<T> extends ResizeComposite implements for (Column<?, T> column : visibleColumns) { final double widthAsIs = column.getWidth(); final boolean isFixedWidth = widthAsIs >= 0; - final double widthFixed = Math.max(widthAsIs, + // Check for max width just to be sure we don't break the limits + final double widthFixed = Math.max( + Math.min(getMaxWidth(column), widthAsIs), column.getMinimumWidth()); defaultExpandRatios = defaultExpandRatios && (column.getExpandRatio() == -1 || column == selectionColumn); @@ -2755,8 +3137,9 @@ public class Grid<T> extends ResizeComposite implements for (Column<?, T> column : nonFixedColumns) { final int expandRatio = (defaultExpandRatios ? 1 : column .getExpandRatio()); - final double newWidth = column.getWidthActual(); final double maxWidth = getMaxWidth(column); + final double newWidth = Math.min(maxWidth, + column.getWidthActual()); boolean shouldExpand = newWidth < maxWidth && expandRatio > 0 && column != selectionColumn; if (shouldExpand) { @@ -2775,6 +3158,10 @@ public class Grid<T> extends ResizeComposite implements double pixelsToDistribute = escalator.getInnerWidth() - reservedPixels; if (pixelsToDistribute <= 0 || totalRatios <= 0) { + if (pixelsToDistribute <= 0) { + // Set column sizes for expanding columns + setColumnSizes(columnSizes); + } return; } @@ -3222,7 +3609,6 @@ public class Grid<T> extends ResizeComposite implements clickOutsideToCloseHandlerRegistration = Event .addNativePreviewHandler(clickOutsideToCloseHandler); } - openCloseButton.setHeight(""); } /** @@ -3251,46 +3637,6 @@ public class Grid<T> extends ResizeComposite implements return content.getParent() == rootContainer; } - /** - * Adds or moves the given widget to the end of the sidebar. - * - * @param widget - * the widget to add or move - */ - public void add(Widget widget) { - content.add(widget); - updateVisibility(); - } - - /** - * Removes the given widget from the sidebar. - * - * @param widget - * the widget to remove - */ - public void remove(Widget widget) { - content.remove(widget); - // updateVisibility is called by remove listener - } - - /** - * Inserts given widget to the given index inside the sidebar. If the - * widget is already in the sidebar, then it is moved to the new index. - * <p> - * See - * {@link FlowPanel#insert(com.google.gwt.user.client.ui.IsWidget, int)} - * for further details. - * - * @param widget - * the widget to insert - * @param beforeIndex - * 0-based index position for the widget. - */ - public void insert(Widget widget, int beforeIndex) { - content.insert(widget, beforeIndex); - updateVisibility(); - } - @Override public void setStylePrimaryName(String styleName) { super.setStylePrimaryName(styleName); @@ -3536,10 +3882,6 @@ public class Grid<T> extends ResizeComposite implements private final AutoColumnWidthsRecalculator autoColumnWidthsRecalculator = new AutoColumnWidthsRecalculator(); private boolean enabled = true; - private double lastTouchEventTime = 0; - private int lastTouchEventX = -1; - private int lastTouchEventY = -1; - private int lastTouchEventRow = -1; private DetailsGenerator detailsGenerator = DetailsGenerator.NULL; private GridSpacerUpdater gridSpacerUpdater = new GridSpacerUpdater(); @@ -3725,8 +4067,8 @@ public class Grid<T> extends ResizeComposite implements } private boolean isSidebarOnDraggedRow() { - return eventCell.getRowIndex() == 0 && getSidebar().isInDOM() - && !getSidebar().isOpen(); + return eventCell.getRowIndex() == 0 && sidebar.isInDOM() + && !sidebar.isOpen(); } /** @@ -3736,8 +4078,7 @@ public class Grid<T> extends ResizeComposite implements private double getSidebarBoundaryComparedTo(double left) { if (isSidebarOnDraggedRow()) { double absoluteLeft = left + getElement().getAbsoluteLeft(); - double sidebarLeft = getSidebar().getElement() - .getAbsoluteLeft(); + double sidebarLeft = sidebar.getElement().getAbsoluteLeft(); double diff = absoluteLeft - sidebarLeft; if (diff > 0) { @@ -4236,6 +4577,16 @@ public class Grid<T> extends ResizeComposite implements return this; } + /** + * Returns the current header caption for this column + * + * @since + * @return the header caption string + */ + public String getHeaderCaption() { + return headerCaption; + } + private void updateHeader() { HeaderRow row = grid.getHeader().getDefaultRow(); if (row != null) { @@ -4937,7 +5288,7 @@ public class Grid<T> extends ResizeComposite implements @Override public void preDetach(Row row, Iterable<FlyweightCell> cellsToDetach) { for (FlyweightCell cell : cellsToDetach) { - Renderer renderer = findRenderer(cell); + Renderer<?> renderer = findRenderer(cell); if (renderer instanceof WidgetRenderer) { try { Widget w = WidgetUtil.findWidget(cell.getElement() @@ -4967,7 +5318,7 @@ public class Grid<T> extends ResizeComposite implements // any more rowReference.set(rowIndex, null, row.getElement()); for (FlyweightCell cell : detachedCells) { - Renderer renderer = findRenderer(cell); + Renderer<?> renderer = findRenderer(cell); if (renderer instanceof ComplexRenderer) { try { Column<?, T> column = getVisibleColumn(cell.getColumn()); @@ -5211,7 +5562,7 @@ public class Grid<T> extends ResizeComposite implements sinkEvents(getHeader().getConsumedEvents()); sinkEvents(Arrays.asList(BrowserEvents.KEYDOWN, BrowserEvents.KEYUP, BrowserEvents.KEYPRESS, BrowserEvents.DBLCLICK, - BrowserEvents.MOUSEDOWN)); + BrowserEvents.MOUSEDOWN, BrowserEvents.CLICK)); // Make ENTER and SHIFT+ENTER in the header perform sorting addHeaderKeyUpHandler(new HeaderKeyUpHandler() { @@ -5353,6 +5704,35 @@ public class Grid<T> extends ResizeComposite implements } /** + * Try to focus a cell by row and column index. Purely internal method. + * + * @param rowIndex + * row index from Editor + * @param columnIndex + * column index from Editor + */ + void focusCell(int rowIndex, int columnIndex) { + RowReference<T> row = new RowReference<T>(this); + Range visibleRows = escalator.getVisibleRowRange(); + + TableRowElement rowElement; + if (visibleRows.contains(rowIndex)) { + rowElement = escalator.getBody().getRowElement(rowIndex); + } else { + getLogger().warning("Row index was not among visible row range"); + return; + } + row.set(rowIndex, dataSource.getRow(rowIndex), rowElement); + + CellReference<T> cell = new CellReference<T>(row); + cell.set(columnIndex, columnIndex, getVisibleColumn(columnIndex)); + + cellFocusHandler.setCellFocus(cell); + + WidgetUtil.focus(getElement()); + } + + /** * Refreshes all header rows */ void refreshHeader() { @@ -5883,10 +6263,23 @@ public class Grid<T> extends ResizeComposite implements return footer.isVisible(); } - protected Editor<T> getEditor() { + public Editor<T> getEditor() { return editor; } + /** + * Add handler for editor open/move/close events + * + * @param handler + * editor handler object + * @return a {@link HandlerRegistration} object that can be used to remove + * the event handler + */ + public HandlerRegistration addEditorEventHandler(EditorEventHandler handler) { + return addHandler(handler, EditorEvent.TYPE); + + } + protected Escalator getEscalator() { return escalator; } @@ -6051,7 +6444,12 @@ public class Grid<T> extends ResizeComposite implements } private void updateFrozenColumns() { - int numberOfColumns = frozenColumnCount; + escalator.getColumnConfiguration().setFrozenColumnCount( + getVisibleFrozenColumnCount()); + } + + private int getVisibleFrozenColumnCount() { + int numberOfColumns = getFrozenColumnCount(); // for the escalator the hidden columns are not in the frozen column // count, but for grid they are. thus need to convert the index @@ -6066,9 +6464,7 @@ public class Grid<T> extends ResizeComposite implements } else if (selectionColumn != null) { numberOfColumns++; } - - escalator.getColumnConfiguration() - .setFrozenColumnCount(numberOfColumns); + return numberOfColumns; } /** @@ -6347,6 +6743,14 @@ public class Grid<T> extends ResizeComposite implements return; } + String eventType = event.getType(); + + if (eventType.equals(BrowserEvents.FOCUS) + || eventType.equals(BrowserEvents.BLUR)) { + super.onBrowserEvent(event); + return; + } + EventTarget target = event.getEventTarget(); if (!Element.is(target) || isOrContainsInSpacer(Element.as(target))) { @@ -6357,7 +6761,6 @@ public class Grid<T> extends ResizeComposite implements RowContainer container = escalator.findRowContainer(e); Cell cell; - String eventType = event.getType(); if (container == null) { if (eventType.equals(BrowserEvents.KEYDOWN) || eventType.equals(BrowserEvents.KEYUP) @@ -6387,7 +6790,7 @@ public class Grid<T> extends ResizeComposite implements eventCell.set(cell, getSectionFromContainer(container)); // Editor can steal focus from Grid and is still handled - if (handleEditorEvent(event, container)) { + if (isEditorEnabled() && handleEditorEvent(event, container)) { return; } @@ -6465,50 +6868,8 @@ public class Grid<T> extends ResizeComposite implements } private boolean handleEditorEvent(Event event, RowContainer container) { - - final boolean closeEvent = event.getTypeInt() == Event.ONKEYDOWN - && event.getKeyCode() == Editor.KEYCODE_HIDE; - - double now = Duration.currentTimeMillis(); - int currentX = WidgetUtil.getTouchOrMouseClientX(event); - int currentY = WidgetUtil.getTouchOrMouseClientY(event); - - final boolean validTouchOpenEvent = event.getTypeInt() == Event.ONTOUCHEND - && now - lastTouchEventTime < 500 - && lastTouchEventRow == eventCell.getRowIndex() - && Math.abs(lastTouchEventX - currentX) < 20 - && Math.abs(lastTouchEventY - currentY) < 20; - - final boolean openEvent = event.getTypeInt() == Event.ONDBLCLICK - || (event.getTypeInt() == Event.ONKEYDOWN && event.getKeyCode() == Editor.KEYCODE_SHOW) - || validTouchOpenEvent; - - if (event.getTypeInt() == Event.ONTOUCHSTART) { - lastTouchEventX = currentX; - lastTouchEventY = currentY; - } - - if (event.getTypeInt() == Event.ONTOUCHEND) { - lastTouchEventTime = now; - lastTouchEventRow = eventCell.getRowIndex(); - } - - if (editor.getState() != Editor.State.INACTIVE) { - if (closeEvent) { - editor.cancel(); - FocusUtil.setFocus(this, true); - } - return true; - } - - if (container == escalator.getBody() && editor.isEnabled() && openEvent) { - editor.editRow(eventCell.getRowIndex(), - eventCell.getColumnIndexDOM()); - event.preventDefault(); - return true; - } - - return false; + return getEditor().getEventHandler().handleEvent( + new EditorDomEvent<T>(event, getEventCell())); } private boolean handleRendererEvent(Event event, RowContainer container) { @@ -6703,11 +7064,21 @@ public class Grid<T> extends ResizeComposite implements if (args.getIndicesLength() == 0) { return editor.editorOverlay; - } else if (args.getIndicesLength() == 1 - && args.getIndex(0) < columns.size()) { - escalator - .scrollToColumn(args.getIndex(0), ScrollDestination.ANY, 0); - return editor.getWidget(columns.get(args.getIndex(0))).getElement(); + } else if (args.getIndicesLength() == 1) { + int index = args.getIndex(0); + if (index >= columns.size()) { + return null; + } + + escalator.scrollToColumn(index, ScrollDestination.ANY, 0); + Widget widget = editor.getWidget(columns.get(index)); + + if (widget != null) { + return widget.getElement(); + } + + // No widget for the column. + return null; } return null; @@ -6863,12 +7234,12 @@ public class Grid<T> extends ResizeComposite implements * if the current selection model is not an instance of * {@link SelectionModel.Single} or {@link SelectionModel.Multi} */ - @SuppressWarnings("unchecked") public boolean select(T row) { if (selectionModel instanceof SelectionModel.Single<?>) { return ((SelectionModel.Single<T>) selectionModel).select(row); } else if (selectionModel instanceof SelectionModel.Multi<?>) { - return ((SelectionModel.Multi<T>) selectionModel).select(row); + return ((SelectionModel.Multi<T>) selectionModel) + .select(Collections.singleton(row)); } else { throw new IllegalStateException("Unsupported selection model"); } @@ -6888,12 +7259,12 @@ public class Grid<T> extends ResizeComposite implements * if the current selection model is not an instance of * {@link SelectionModel.Single} or {@link SelectionModel.Multi} */ - @SuppressWarnings("unchecked") public boolean deselect(T row) { if (selectionModel instanceof SelectionModel.Single<?>) { return ((SelectionModel.Single<T>) selectionModel).deselect(row); } else if (selectionModel instanceof SelectionModel.Multi<?>) { - return ((SelectionModel.Multi<T>) selectionModel).deselect(row); + return ((SelectionModel.Multi<T>) selectionModel) + .deselect(Collections.singleton(row)); } else { throw new IllegalStateException("Unsupported selection model"); } @@ -7670,6 +8041,12 @@ public class Grid<T> extends ResizeComposite implements if (escalator.getInnerWidth() != autoColumnWidthsRecalculator.lastCalculatedInnerWidth) { recalculateColumnWidths(); } + + // Vertical resizing could make editor positioning invalid so it + // needs to be recalculated on resize + if (isEditorActive()) { + editor.updateVerticalScrollPosition(); + } } }); } @@ -7775,15 +8152,15 @@ public class Grid<T> extends ResizeComposite implements @Override protected void doAttachChildren() { - if (getSidebar().getParent() == this) { - onAttach(getSidebar()); + if (sidebar.getParent() == this) { + onAttach(sidebar); } } @Override protected void doDetachChildren() { - if (getSidebar().getParent() == this) { - onDetach(getSidebar()); + if (sidebar.getParent() == this) { + onDetach(sidebar); } } @@ -7815,6 +8192,10 @@ public class Grid<T> extends ResizeComposite implements "Details generator may not be null"); } + for (Integer index : visibleDetails) { + setDetailsVisible(index, false); + } + this.detailsGenerator = detailsGenerator; // this will refresh all visible spacers @@ -7846,6 +8227,10 @@ public class Grid<T> extends ResizeComposite implements * @see #isDetailsVisible(int) */ public void setDetailsVisible(int rowIndex, boolean visible) { + if (DetailsGenerator.NULL.equals(detailsGenerator)) { + return; + } + Integer rowIndexInteger = Integer.valueOf(rowIndex); /* @@ -7904,19 +8289,6 @@ public class Grid<T> extends ResizeComposite implements } /** - * Returns the sidebar for this grid. - * <p> - * The grid's sidebar shows the column hiding options for those columns that - * have been set as {@link Column#setHidable(boolean) hidable}. - * - * @since 7.5.0 - * @return the sidebar widget for this grid - */ - private Sidebar getSidebar() { - return sidebar; - } - - /** * Gets the customizable menu bar that is by default used for toggling * column hidability. The application developer is allowed to add their * custom items to the end of the menu, but should try to avoid modifying @@ -7962,6 +8334,54 @@ public class Grid<T> extends ResizeComposite implements } } + @Override + public int getTabIndex() { + return FocusUtil.getTabIndex(this); + } + + @Override + public void setAccessKey(char key) { + FocusUtil.setAccessKey(this, key); + } + + @Override + public void setFocus(boolean focused) { + FocusUtil.setFocus(this, focused); + } + + @Override + public void setTabIndex(int index) { + FocusUtil.setTabIndex(this, index); + } + + @Override + public void focus() { + setFocus(true); + } + + /** + * Sets the buffered editor mode. + * + * @since 7.6 + * @param editorUnbuffered + * <code>true</code> to enable buffered editor, + * <code>false</code> to disable it + */ + public void setEditorBuffered(boolean editorBuffered) { + editor.setBuffered(editorBuffered); + } + + /** + * Gets the buffered editor mode. + * + * @since 7.6 + * @return <code>true</code> if buffered editor is enabled, + * <code>false</code> otherwise + */ + public boolean isEditorBuffered() { + return editor.isBuffered(); + } + /** * Returns the {@link EventCellReference} for the latest event fired from * this Grid. @@ -7974,4 +8394,26 @@ public class Grid<T> extends ResizeComposite implements public EventCellReference<T> getEventCell() { return eventCell; } + + /** + * Returns a CellReference for the cell to which the given element belongs + * to. + * + * @since 7.6 + * @param element + * Element to find from the cell's content. + * @return CellReference or <code>null</code> if cell was not found. + */ + public CellReference<T> getCellReference(Element element) { + RowContainer container = getEscalator().findRowContainer(element); + if (container != null) { + Cell cell = container.getCell(element); + if (cell != null) { + EventCellReference<T> cellRef = new EventCellReference<T>(this); + cellRef.set(cell, getSectionFromContainer(container)); + return cellRef; + } + } + return null; + } } diff --git a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java b/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java index 62b727e5f5..24bf9b6558 100644 --- a/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java +++ b/client/tests/src/com/vaadin/client/VBrowserDetailsUserAgentParserTest.java @@ -56,6 +56,8 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { private static final String ANDROID_MOTOROLA_3_0 = "Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13"; private static final String ANDROID_GALAXY_NEXUS_4_0_4_CHROME = "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19"; + private static final String EDGE_WINDOWS_10 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240"; + public void testSafari3() { VBrowserDetails bd = new VBrowserDetails(SAFARI3_WINDOWS); assertWebKit(bd); @@ -423,6 +425,14 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertWindows(bd, true); } + public void testEdgeWindows10() { + VBrowserDetails bd = new VBrowserDetails(EDGE_WINDOWS_10); + assertEdge(bd); + assertBrowserMajorVersion(bd, 12); + assertBrowserMinorVersion(bd, 10240); + assertWindows(bd, false); + } + /* * Helper methods below */ @@ -484,6 +494,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertChrome(VBrowserDetails browserDetails) { @@ -493,6 +504,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertIE(VBrowserDetails browserDetails) { @@ -502,6 +514,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertTrue(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertOpera(VBrowserDetails browserDetails) { @@ -511,6 +524,7 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertTrue(browserDetails.isOpera()); assertFalse(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); } private void assertSafari(VBrowserDetails browserDetails) { @@ -520,6 +534,17 @@ public class VBrowserDetailsUserAgentParserTest extends TestCase { assertFalse(browserDetails.isIE()); assertFalse(browserDetails.isOpera()); assertTrue(browserDetails.isSafari()); + assertFalse(browserDetails.isEdge()); + } + + private void assertEdge(VBrowserDetails browserDetails) { + // Browser + assertFalse(browserDetails.isFirefox()); + assertFalse(browserDetails.isChrome()); + assertFalse(browserDetails.isIE()); + assertFalse(browserDetails.isOpera()); + assertFalse(browserDetails.isSafari()); + assertTrue(browserDetails.isEdge()); } private void assertMacOSX(VBrowserDetails browserDetails) { diff --git a/eclipse/Super Development Mode (vaadin).launch b/eclipse/Super Development Mode (vaadin).launch index 361a456e96..b03337e5ff 100644 --- a/eclipse/Super Development Mode (vaadin).launch +++ b/eclipse/Super Development Mode (vaadin).launch @@ -23,5 +23,5 @@ <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.codeserver.CodeServer"/> <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-noprecompile -strict -bindAddress 0.0.0.0 com.vaadin.DefaultWidgetSet com.vaadin.tests.widgetset.TestingWidgetSet"/> <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="vaadin"/> -<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx512M -XX:MaxPermSize=256M"/> +<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx1G -XX:MaxPermSize=256M"/> </launchConfiguration> diff --git a/push/ivy.xml b/push/ivy.xml index faf96818e1..180eeac7b8 100644 --- a/push/ivy.xml +++ b/push/ivy.xml @@ -3,7 +3,7 @@ <!-- Keep the version number in sync with build.xml --> <!ENTITY atmosphere.runtime.version "2.2.7.vaadin1"> - <!ENTITY atmosphere.js.version "2.2.6.vaadin4"> + <!ENTITY atmosphere.js.version "2.2.6.vaadin5"> ]> <ivy-module version="2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" diff --git a/scripts/DeployHelpers.py b/scripts/DeployHelpers.py index 2c879088ff..12c7baaaec 100644 --- a/scripts/DeployHelpers.py +++ b/scripts/DeployHelpers.py @@ -12,14 +12,51 @@ except Exception as e: from requests.auth import HTTPDigestAuth from os.path import join, expanduser, basename from BuildHelpers import parser, getArgs +from time import sleep parser.add_argument("--deployUrl", help="Wildfly management URL") parser.add_argument("--deployUser", help="Deployment user", default=None) parser.add_argument("--deployPass", help="Deployment password", default=None) +serverUp = None + +def testServer(): + global serverUp + + if serverUp is not None: + return serverUp + + print("Checking server status") + i = 0 + request = {"operation" : "read-attribute", "name" : "server-state"} + serverUp = False + while not serverUp and i < 2: + try: + print("Trying on url %s" % (getUrl())) + result = doPostJson(url=getUrl(), auth=getAuth(), data=json.dumps(request)) + response = result.json() + if "outcome" not in response or response["outcome"] != "success": + # Failure + raise Exception(response) + elif "result" not in response or response["result"] != "running": + # Another failure + raise Exception(response) + # All OK + serverUp = True + print("Got server connection.") + except Exception as e: + print("Exception while checking server state: ", e) + print("Server connection failed, retrying in 5 seconds.") + i = i + 1 + sleep(5) + return serverUp + # Helper for handling the full deployment # name should end with .war def deployWar(warFile, name=None): + if not testServer(): + raise Exception("Server not up. Skipping deployment.") + return if name is None: name = basename(warFile).replace('.war', "-%s.war" % (getArgs().version.split('-')[0])) diff --git a/scripts/GenerateBuildReport.py b/scripts/GenerateBuildReport.py index 8ee2472133..eafdb39430 100644 --- a/scripts/GenerateBuildReport.py +++ b/scripts/GenerateBuildReport.py @@ -15,6 +15,7 @@ content = """<html> <body> <table> <tr><td><a href="https://dev.vaadin.com/milestone?action=new">Create milestone for next release</a></td></tr> +<tr><td><a href="https://dev.vaadin.com/query?status=closed&component=Core+Framework&resolution=fixed&milestone=!Vaadin {version}&col=id&col=summary&col=component&col=status&col=type&col=priority&col=milestone&order=priority">Closed fixed tickets without milestone {version}</a></td></tr> <tr><td><a href="https://dev.vaadin.com/query?status=closed&component=Core+Framework&resolution=fixed&milestone=Vaadin {version}&col=id&col=summary&col=component&col=milestone&col=status&col=type">Closed tickets with milestone {version}</a></td></tr> <tr><td><a href="https://dev.vaadin.com/query?status=pending-release&component=Core+Framework&resolution=fixed&milestone=Vaadin {version}&col=id&col=summary&col=component&col=milestone&col=status&col=type">Pending-release tickets with milestone {version}</a></td></tr> <tr><td><a href="https://dev.vaadin.com/query?status=pending-release&milestone=">Pending-release tickets without milestone</a></td></tr> @@ -24,8 +25,8 @@ content = """<html> try: p1 = subprocess.Popen(['find', '.', '-name', '*.java'], stdout=subprocess.PIPE) - p2 = subprocess.Popen(['xargs', 'egrep', '@since ?$'], stdin=p1.stdout, stdout=subprocess.PIPE) - missing = subprocess.check_output(['grep', '-v', 'tests'], stdin=p2.stdout) + p2 = subprocess.Popen(['xargs', 'egrep', '-n', '@since ?$'], stdin=p1.stdout, stdout=subprocess.PIPE) + missing = subprocess.check_output(['egrep', '-v', '/(tests|result)/'], stdin=p2.stdout) content += "<tr><td>Empty @since:<br>\n<pre>%s</pre></td></tr>\n" % (missing) except subprocess.CalledProcessError as e: if e.returncode == 1: @@ -33,13 +34,7 @@ except subprocess.CalledProcessError as e: else: raise e -content += "<tr><td>Try demos<ul>" - -for demo in demos: - content += "<li><a href='{url}/{demoName}-{version}'>{demoName}</a></li>\n".format(url=args.deployUrl, demoName=demo, version=args.version) - -content += """</ul></td></tr> -<tr><td><a href="{url}">Build result page (See test results, pin and tag build and dependencies)</a></td></tr> +content += """<tr><td><a href="{url}">Build result page (See test results, pin and tag build and dependencies)</a></td></tr> </table> </body> </html>""".format(url=args.buildResultUrl) diff --git a/scripts/GeneratePublishReport.py b/scripts/GeneratePublishReport.py new file mode 100644 index 0000000000..6cd0791f24 --- /dev/null +++ b/scripts/GeneratePublishReport.py @@ -0,0 +1,65 @@ +#coding=UTF-8 + +import argparse, cgi +from os.path import exists, isdir +from os import makedirs + +parser = argparse.ArgumentParser(description="Post-publish report generator") +parser.add_argument("version", type=str, help="Vaadin version that was just built") +parser.add_argument("buildResultUrl", type=str, help="URL for the build result page") + +args = parser.parse_args() + +resultPath = "result" +if not exists(resultPath): + makedirs(resultPath) +elif not isdir(resultPath): + print("Result path is not a directory.") + sys.exit(1) + +(major, minor, maintenance) = args.version.split(".", 2) +prerelease = "." in maintenance +if prerelease: + maintenance = maintenance.split('.')[0] + +content = """<html> +<head></head> +<body> +<table> +""" + +if not prerelease: + content += "<tr><td><a href='http://vaadin.com/download/release/{maj}.{min}/{ver}/'>Check {ver} is published to vaadin.com/download</td></tr>".format(maj=major, min=minor, ver=args.version) + content += "<tr><td><a href='http://repo1.maven.org/maven2/com/vaadin/vaadin-server/{ver}'>Check {ver} is published to maven.org (might take a while)</td></tr>".format(ver=args.version) +else: + content += "<tr><td><a href='http://vaadin.com/download/prerelease/{maj}.{min}/{maj}.{min}.{main}/{ver}'>Check {ver} is published as prerelease to vaadin.com/download</td></tr>".format(maj=major, min=minor, main=maintenance, ver=args.version) + content += "<tr><td><a href='http://maven.vaadin.com/vaadin-prereleases/com/vaadin/vaadin-server/{ver}'>Check {ver} is published as prerelease to maven.vaadin.com</td></tr>".format(ver=args.version) + + +content += """ +<tr><td>Verify Latest Vaadin 7: <iframe src="http://vaadin.com/download/LATEST7"></iframe></td></tr> +<tr><td>Verify Vaadin 7 Version List: <iframe src="http://vaadin.com/download/VERSIONS_7"></iframe></td></tr> +<tr><td>Verify Latest Vaadin 7.5: <iframe src="http://vaadin.com/download/release/7.5/LATEST"></iframe></td></tr> +<tr><td>Verify Latest Vaadin 7.6: <iframe src="http://vaadin.com/download/release/7.6/LATEST"></iframe></td></tr> +<tr><td>Verify Latest Vaadin 6: <iframe src="http://vaadin.com/download/LATEST"></iframe></td></tr> +<tr><td>Verify Latest Vaadin 7 Prerelease: <iframe src="http://vaadin.com/download/PRERELEASES"></iframe></td></tr>""" + +if not prerelease: + content += '<tr><td><a href="https://dev.vaadin.com/admin/ticket/versions">Set latest version to default</a></td></tr>' + +content += """ +<tr><td><a href="http://test.vaadin.com/{version}/run/LabelModes?restartApplication">Verify uploaded to test.vaadin.com</a></td></tr> +<tr><td><a href="https://github.com/vaadin/vaadin/tags">Verify tags pushed to GitHub</a></td></tr>""".format(version=args.version) + +if not prerelease: + content += '<tr><td><a href="http://vaadin.com/api">Verify API version list updated</a></td></tr>' + +content += """ +<tr><td><a href="https://dev.vaadin.com/query?status=pending-release&component=Core+Framework&resolution=fixed&milestone=Vaadin {version}&col=id&col=summary&col=component&col=milestone&col=status&col=type">Batch update tickets in Trac</a></td></tr> +<tr><td><a href="{url}">Publish result page (See test results, pin and tag build and dependencies)</a></td></tr> +</table> +</body> +</html>""".format(url=args.buildResultUrl, version=args.version) + +f = open("result/report.html", 'w') +f.write(content) diff --git a/scripts/GenerateStagingReport.py b/scripts/GenerateStagingReport.py index fdcdc93fdb..23977aa933 100644 --- a/scripts/GenerateStagingReport.py +++ b/scripts/GenerateStagingReport.py @@ -1,6 +1,7 @@ #coding=UTF-8 from BuildArchetypes import archetypes, getDeploymentContext +from BuildDemos import demos import argparse, cgi parser = argparse.ArgumentParser(description="Build report generator") @@ -8,6 +9,7 @@ parser.add_argument("version", type=str, help="Vaadin version that was just buil parser.add_argument("deployUrl", type=str, help="Base url of the deployment server") parser.add_argument("buildResultUrl", type=str, help="URL for the build result page") parser.add_argument("stagingRepo", type=str, help="URL for the staging repository") +parser.add_argument("tbapiUrl", type=str, help="URL for the TestBench API build") args = parser.parse_args() @@ -17,7 +19,12 @@ content = """<html> <table> """ -content += "<tr><td>Try archetype demos<ul>" +content += "<tr><td>Try demos<ul>" + +for demo in demos: + content += "<li><a href='{url}/{demoName}-{version}'>{demoName}</a></li>\n".format(url=args.deployUrl, demoName=demo, version=args.version) + +content += "</ul></td></tr>\n<tr><td>Try archetype demos<ul>" for archetype in archetypes: content += "<li><a href='{url}/{context}'>{demo}</a></li>\n".format(url=args.deployUrl, demo=archetype, context=getDeploymentContext(archetype, args.version)) @@ -29,12 +36,48 @@ content += cgi.escape(""" <ibiblio name="vaadin-staging" usepoms="true" m2compat root="{repoUrl}" />""".format(repoUrl=args.stagingRepo)) content += """</pre> </td></tr> -<tr><td><a href="https://dev.vaadin.com/milestone/Vaadin {version}">Trac Milestone</a></td></tr> +<tr><td><a href="https://dev.vaadin.com/milestone/Vaadin {version}">Close Trac Milestone</a></td></tr> +<tr><td><a href="https://dev.vaadin.com/query?status=pending-release&component=Core+Framework&resolution=fixed&col=id&col=summary&col=component&col=milestone&col=status&col=type">Verify pending release tickets still have milestone {version}</a></td></tr> <tr><td><a href="https://dev.vaadin.com/admin/ticket/versions">Add version {version} to Trac</td></tr> <tr><td><a href="{url}">Staging result page (See test results, pin and tag build and dependencies)</a></td></tr> +<tr><td>Commands to tag all repositories (warning: do not run as a single script but set variables and check before any push commands - this has not been tested yet and the change IDs are missing)</td></tr> +<tr><td><pre> +VERSION={version} + +GERRIT_USER=[fill in your gerrit username] +FRAMEWORK_REVISION=[fill in framework revision] +SCREENSHOTS_REVISION=[fill in screenshot repository revision] +ARCHETYPES_REVISION=[fill in maven-integration repository revision] +PLUGIN_REVISION=[fill in maven plug-in repository revision] + +git clone ssh://$GERRIT_USER@dev.vaadin.com:29418/vaadin +cd vaadin +git tag -a -m"$VERSION" $VERSION $FRAMEWORK_REVISION +git push --tags +cd .. + +git clone ssh://$GERRIT_USER@dev.vaadin.com:29418/vaadin-screenshots +cd vaadin-screenshots +git tag -a -m"$VERSION" $VERSION $SCREENSHOTS_REVISION +git push --tags +cd .. + +git clone ssh://$GERRIT_USER@dev.vaadin.com:29418/maven-integration +cd maven-integration +git tag -a -m"$VERSION" $VERSION $ARCHETYPES_REVISION +git push --tags +cd .. + +git clone ssh://$GERRIT_USER@dev.vaadin.com:29418/maven-plugin +cd maven-plugin +git tag -a -m"$VERSION" $VERSION $PLUGIN_REVISION +git push --tags +cd .. +</pre></td></tr> +<tr><td><a href="{tbapi}">Build and publish TestBench API for version {version} if proceeding</a></td></tr> </table> </body> -</html>""".format(url=args.buildResultUrl, repoUrl=args.stagingRepo, version=args.version) +</html>""".format(url=args.buildResultUrl, repoUrl=args.stagingRepo, version=args.version, tbapi=args.tbapiUrl) f = open("result/report.html", 'w') f.write(content) diff --git a/server/build.xml b/server/build.xml index 4bb2bde730..95c0b0add4 100644 --- a/server/build.xml +++ b/server/build.xml @@ -34,7 +34,7 @@ </target> <target name="jar" depends="compress-files"> <property name="server.osgi.import" - value="javax.servlet;version="2.4.0",javax.servlet.http;version="2.4.0",javax.validation;version="1.0.0.GA";resolution:=optional,org.jsoup;version="1.6.3",org.jsoup.parser;version="1.6.3",org.jsoup.nodes;version="1.6.3",org.jsoup.helper;version="1.6.3",org.jsoup.safety;version="1.6.3",org.jsoup.select;version="1.6.3"" /> + value="javax.servlet;version="2.4.0",javax.servlet.http;version="2.4.0",javax.validation;version="1.0.0.GA";resolution:=optional,org.jsoup;version="1.6.3",org.jsoup.parser;version="1.6.3",org.jsoup.nodes;version="1.6.3",org.jsoup.helper;version="1.6.3",org.jsoup.safety;version="1.6.3",org.jsoup.select;version="1.6.3",javax.portlet;version="[2.0,3)";resolution:=optional,javax.portlet.filter;version="[2.0,3)";resolution:=optional,com.liferay.portal.kernel.util;resolution:=optional" /> <property name="server.osgi.require" value="com.google.gwt.thirdparty.guava;bundle-version="16.0.1.vaadin1",com.vaadin.shared;bundle-version="${vaadin.version}",com.vaadin.push;bundle-version="${vaadin.version}";resolution:=optional,com.vaadin.sass-compiler;bundle-version="${vaadin.sass.version}";resolution:=optional" /> <antcall target="common.jar"> diff --git a/server/ivy.xml b/server/ivy.xml index e9bc8b818d..0711b4b2ea 100644 --- a/server/ivy.xml +++ b/server/ivy.xml @@ -36,7 +36,7 @@ <!-- Google App Engine --> <dependency org="com.google.appengine" name="appengine-api-1.0-sdk" - rev="1.2.1" conf="build-provided,ide,test -> default" /> + rev="1.7.7" conf="build-provided,ide,test -> default" /> <!-- Bean Validation API --> <dependency org="javax.validation" name="validation-api" diff --git a/server/src/com/vaadin/data/DataGenerator.java b/server/src/com/vaadin/data/DataGenerator.java new file mode 100644 index 0000000000..a5333b8523 --- /dev/null +++ b/server/src/com/vaadin/data/DataGenerator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.data; + +import java.io.Serializable; + +import com.vaadin.ui.Grid.AbstractGridExtension; +import com.vaadin.ui.Grid.AbstractRenderer; + +import elemental.json.JsonObject; + +/** + * Interface for {@link AbstractGridExtension}s that allows adding data to row + * objects being sent to client by the {@link RpcDataProviderExtension}. + * <p> + * {@link AbstractRenderer} implements this interface to provide encoded data to + * client for {@link Renderer}s automatically. + * + * @since 7.6 + * @author Vaadin Ltd + */ +public interface DataGenerator extends Serializable { + + /** + * Adds data to row object for given item and item id being sent to client. + * + * @param itemId + * item id of item + * @param item + * item being sent to client + * @param rowData + * row object being sent to client + */ + public void generateData(Object itemId, Item item, JsonObject rowData); + +} diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index b3c7972b52..78c87ab23d 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -23,14 +23,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; -import java.util.logging.Logger; -import com.google.gwt.thirdparty.guava.common.collect.BiMap; -import com.google.gwt.thirdparty.guava.common.collect.HashBiMap; import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet; import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Sets; @@ -43,31 +38,23 @@ import com.vaadin.data.Container.ItemSetChangeNotifier; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.Property.ValueChangeNotifier; -import com.vaadin.data.util.converter.Converter; import com.vaadin.server.AbstractExtension; import com.vaadin.server.ClientConnector; import com.vaadin.server.KeyMapper; import com.vaadin.shared.data.DataProviderRpc; import com.vaadin.shared.data.DataRequestRpc; -import com.vaadin.shared.ui.grid.DetailsConnectorChange; import com.vaadin.shared.ui.grid.GridClientRpc; import com.vaadin.shared.ui.grid.GridState; import com.vaadin.shared.ui.grid.Range; -import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Component; import com.vaadin.ui.Grid; -import com.vaadin.ui.Grid.CellReference; -import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; import com.vaadin.ui.Grid.DetailsGenerator; import com.vaadin.ui.Grid.RowReference; -import com.vaadin.ui.Grid.RowStyleGenerator; -import com.vaadin.ui.renderers.Renderer; import elemental.json.Json; import elemental.json.JsonArray; import elemental.json.JsonObject; -import elemental.json.JsonValue; /** * Provides Vaadin server-side container data source to a @@ -82,445 +69,84 @@ import elemental.json.JsonValue; public class RpcDataProviderExtension extends AbstractExtension { /** - * ItemId to Key to ItemId mapper. - * <p> - * This class is used when transmitting information about items in container - * related to Grid. It introduces a consistent way of mapping ItemIds and - * its container to a String that can be mapped back to ItemId. - * <p> - * <em>Technical note:</em> This class also keeps tabs on which indices are - * being shown/selected, and is able to clean up after itself once the - * itemId ⇆ key mapping is not needed anymore. In other words, this - * doesn't leak memory. + * Class for keeping track of current items and ValueChangeListeners. + * + * @since 7.6 */ - public class DataProviderKeyMapper implements Serializable { - private final BiMap<Object, String> itemIdToKey = HashBiMap.create(); - private Set<Object> pinnedItemIds = new HashSet<Object>(); - private long rollingIndex = 0; - - private DataProviderKeyMapper() { - // private implementation - } - - /** - * Sets the currently active rows. This will purge any unpinned rows - * from cache. - * - * @param itemIds - * collection of itemIds to map to row keys - */ - void setActiveRows(Collection<?> itemIds) { - Set<Object> itemSet = new HashSet<Object>(itemIds); - Set<Object> itemsRemoved = new HashSet<Object>(); - for (Object itemId : itemIdToKey.keySet()) { - if (!itemSet.contains(itemId) && !isPinned(itemId)) { - itemsRemoved.add(itemId); - } - } - - for (Object itemId : itemsRemoved) { - detailComponentManager.destroyDetails(itemId); - itemIdToKey.remove(itemId); - } - - for (Object itemId : itemSet) { - itemIdToKey.put(itemId, getKey(itemId)); - if (visibleDetails.contains(itemId)) { - detailComponentManager.createDetails(itemId, - indexOf(itemId)); - } - } - } - - private String nextKey() { - return String.valueOf(rollingIndex++); - } + private class ActiveItemHandler implements Serializable, DataGenerator { - /** - * Gets the key for a given item id. Creates a new key mapping if no - * existing mapping was found for the given item id. - * - * @since 7.5.0 - * @param itemId - * the item id to get the key for - * @return the key for the given item id - */ - public String getKey(Object itemId) { - String key = itemIdToKey.get(itemId); - if (key == null) { - key = nextKey(); - itemIdToKey.put(itemId, key); - } - return key; - } + private final Map<Object, GridValueChangeListener> activeItemMap = new HashMap<Object, GridValueChangeListener>(); + private final KeyMapper<Object> keyMapper = new KeyMapper<Object>(); + private final Set<Object> droppedItems = new HashSet<Object>(); /** - * Gets keys for a collection of item ids. + * Registers ValueChangeListeners for given item ids. * <p> - * If the itemIds are currently cached, the existing keys will be used. - * Otherwise new ones will be created. + * Note: This method will clean up any unneeded listeners and key + * mappings * * @param itemIds - * the item ids for which to get keys - * @return keys for the {@code itemIds} + * collection of new active item ids */ - public List<String> getKeys(Collection<Object> itemIds) { - if (itemIds == null) { - throw new IllegalArgumentException("itemIds can't be null"); - } - - ArrayList<String> keys = new ArrayList<String>(itemIds.size()); + public void addActiveItems(Collection<?> itemIds) { for (Object itemId : itemIds) { - keys.add(getKey(itemId)); - } - return keys; - } - - /** - * Gets the registered item id based on its key. - * <p> - * A key is used to identify a particular row on both a server and a - * client. This method can be used to get the item id for the row key - * that the client has sent. - * - * @param key - * the row key for which to retrieve an item id - * @return the item id corresponding to {@code key} - * @throws IllegalStateException - * if the key mapper does not have a record of {@code key} . - */ - public Object getItemId(String key) throws IllegalStateException { - Object itemId = itemIdToKey.inverse().get(key); - if (itemId != null) { - return itemId; - } else { - throw new IllegalStateException("No item id for key " + key - + " found."); - } - } - - /** - * Gets corresponding item ids for each of the keys in a collection. - * - * @param keys - * the keys for which to retrieve item ids - * @return a collection of item ids for the {@code keys} - * @throws IllegalStateException - * if one or more of keys don't have a corresponding item id - * in the cache - */ - public Collection<Object> getItemIds(Collection<String> keys) - throws IllegalStateException { - if (keys == null) { - throw new IllegalArgumentException("keys may not be null"); - } - - ArrayList<Object> itemIds = new ArrayList<Object>(keys.size()); - for (String key : keys) { - itemIds.add(getItemId(key)); - } - return itemIds; - } - - /** - * Pin an item id to be cached indefinitely. - * <p> - * Normally when an itemId is not an active row, it is discarded from - * the cache. Pinning an item id will make sure that it is kept in the - * cache. - * <p> - * In effect, while an item id is pinned, it always has the same key. - * - * @param itemId - * the item id to pin - * @throws IllegalStateException - * if {@code itemId} was already pinned - * @see #unpin(Object) - * @see #isPinned(Object) - * @see #getItemIds(Collection) - */ - public void pin(Object itemId) throws IllegalStateException { - if (isPinned(itemId)) { - throw new IllegalStateException("Item id " + itemId - + " was pinned already"); - } - pinnedItemIds.add(itemId); - } - - /** - * Unpin an item id. - * <p> - * This cancels the effect of pinning an item id. If the item id is - * currently inactive, it will be immediately removed from the cache. - * - * @param itemId - * the item id to unpin - * @throws IllegalStateException - * if {@code itemId} was not pinned - * @see #pin(Object) - * @see #isPinned(Object) - * @see #getItemIds(Collection) - */ - public void unpin(Object itemId) throws IllegalStateException { - if (!isPinned(itemId)) { - throw new IllegalStateException("Item id " + itemId - + " was not pinned"); + if (!activeItemMap.containsKey(itemId)) { + activeItemMap.put(itemId, new GridValueChangeListener( + itemId, container.getItem(itemId))); + } } - pinnedItemIds.remove(itemId); + // Remove still active rows that were "dropped" + droppedItems.removeAll(itemIds); + internalDropActiveItems(droppedItems); + droppedItems.clear(); } /** - * Checks whether an item id is pinned or not. + * Marks given item id as dropped. Dropped items are cleared when adding + * new active items. * * @param itemId - * the item id to check for pin status - * @return {@code true} iff the item id is currently pinned - */ - public boolean isPinned(Object itemId) { - return pinnedItemIds.contains(itemId); - } - } - - /** - * A helper class that handles the client-side Escalator logic relating to - * making sure that whatever is currently visible to the user, is properly - * initialized and otherwise handled on the server side (as far as - * required). - * <p> - * This bookeeping includes, but is not limited to: - * <ul> - * <li>listening to the currently visible {@link com.vaadin.data.Property - * Properties'} value changes on the server side and sending those back to - * the client; and - * <li>attaching and detaching {@link com.vaadin.ui.Component Components} - * from the Vaadin Component hierarchy. - * </ul> - */ - private class ActiveRowHandler implements Serializable { - /** - * A map from index to the value change listener used for all of column - * properties - */ - private final Map<Integer, GridValueChangeListener> valueChangeListeners = new HashMap<Integer, GridValueChangeListener>(); - - /** - * The currently active range. Practically, it's the range of row - * indices being cached currently. - */ - private Range activeRange = Range.withLength(0, 0); - - /** - * A hook for making sure that appropriate data is "active". All other - * rows should be "inactive". - * <p> - * "Active" can mean different things in different contexts. For - * example, only the Properties in the active range need - * ValueChangeListeners. Also, whenever a row with a Component becomes - * active, it needs to be attached (and conversely, when inactive, it - * needs to be detached). - * - * @param firstActiveRow - * the first active row - * @param activeRowCount - * the number of active rows - */ - public void setActiveRows(Range newActiveRange) { - - // TODO [[Components]] attach and detach components - - /*- - * Example - * - * New Range: [3, 4, 5, 6, 7] - * Old Range: [1, 2, 3, 4, 5] - * Result: [1, 2][3, 4, 5] [] - */ - final Range[] depractionPartition = activeRange - .partitionWith(newActiveRange); - removeValueChangeListeners(depractionPartition[0]); - removeValueChangeListeners(depractionPartition[2]); - - /*- - * Example - * - * Old Range: [1, 2, 3, 4, 5] - * New Range: [3, 4, 5, 6, 7] - * Result: [] [3, 4, 5][6, 7] - */ - final Range[] activationPartition = newActiveRange - .partitionWith(activeRange); - addValueChangeListeners(activationPartition[0]); - addValueChangeListeners(activationPartition[2]); - - activeRange = newActiveRange; - - assert valueChangeListeners.size() == newActiveRange.length() : "Value change listeners not set up correctly!"; - } - - private void addValueChangeListeners(Range range) { - for (Integer i = range.getStart(); i < range.getEnd(); i++) { - - final Object itemId = container.getIdByIndex(i); - final Item item = container.getItem(itemId); - - assert valueChangeListeners.get(i) == null : "Overwriting existing listener"; - - GridValueChangeListener listener = new GridValueChangeListener( - itemId, item); - valueChangeListeners.put(i, listener); - } - } - - private void removeValueChangeListeners(Range range) { - for (Integer i = range.getStart(); i < range.getEnd(); i++) { - final GridValueChangeListener listener = valueChangeListeners - .remove(i); - - assert listener != null : "Trying to remove nonexisting listener"; - - listener.removeListener(); - } - } - - /** - * Manages removed columns in active rows. - * <p> - * This method does <em>not</em> send data again to the client. - * - * @param removedColumns - * the columns that have been removed from the grid + * dropped item id */ - public void columnsRemoved(Collection<Column> removedColumns) { - if (removedColumns.isEmpty()) { - return; - } - - for (GridValueChangeListener listener : valueChangeListeners - .values()) { - listener.removeColumns(removedColumns); + public void dropActiveItem(Object itemId) { + if (activeItemMap.containsKey(itemId)) { + droppedItems.add(itemId); } } - /** - * Manages added columns in active rows. - * <p> - * This method sends the data for the changed rows to client side. - * - * @param addedColumns - * the columns that have been added to the grid - */ - public void columnsAdded(Collection<Column> addedColumns) { - if (addedColumns.isEmpty()) { - return; - } + private void internalDropActiveItems(Collection<Object> itemIds) { + for (Object itemId : droppedItems) { + assert activeItemMap.containsKey(itemId) : "Item ID should exist in the activeItemMap"; - for (GridValueChangeListener listener : valueChangeListeners - .values()) { - listener.addColumns(addedColumns); + activeItemMap.remove(itemId).removeListener(); + keyMapper.remove(itemId); } } /** - * Handles the insertion of rows. - * <p> - * This method's responsibilities are to: - * <ul> - * <li>shift the internal bookkeeping by <code>count</code> if the - * insertion happens above currently active range - * <li>ignore rows inserted below the currently active range - * <li>shift (and deactivate) rows pushed out of view - * <li>activate rows that are inserted in the current viewport - * </ul> + * Gets a collection copy of currently active item ids. * - * @param firstIndex - * the index of the first inserted rows - * @param count - * the number of rows inserted at <code>firstIndex</code> + * @return collection of item ids */ - public void insertRows(int firstIndex, int count) { - if (firstIndex < activeRange.getStart()) { - moveListeners(activeRange, count); - activeRange = activeRange.offsetBy(count); - } else if (firstIndex < activeRange.getEnd()) { - int end = activeRange.getEnd(); - // Move rows from first added index by count - Range movedRange = Range.between(firstIndex, end); - moveListeners(movedRange, count); - // Remove excess listeners from extra rows - removeValueChangeListeners(Range.withLength(end, count)); - // Add listeners for new rows - final Range freshRange = Range.withLength(firstIndex, count); - addValueChangeListeners(freshRange); - } else { - // out of view, noop - } + public Collection<Object> getActiveItemIds() { + return new HashSet<Object>(activeItemMap.keySet()); } /** - * Handles the removal of rows. - * <p> - * This method's responsibilities are to: - * <ul> - * <li>shift the internal bookkeeping by <code>count</code> if the - * removal happens above currently active range - * <li>ignore rows removed below the currently active range - * </ul> + * Gets a collection copy of currently active ValueChangeListeners. * - * @param firstIndex - * the index of the first removed rows - * @param count - * the number of rows removed at <code>firstIndex</code> + * @return collection of value change listeners */ - public void removeRows(int firstIndex, int count) { - Range removed = Range.withLength(firstIndex, count); - if (removed.intersects(activeRange)) { - final Range[] deprecated = activeRange.partitionWith(removed); - // Remove the listeners that are no longer existing - removeValueChangeListeners(deprecated[1]); - - // Move remaining listeners to fill the listener map correctly - moveListeners(deprecated[2], -deprecated[1].length()); - activeRange = Range.withLength(activeRange.getStart(), - activeRange.length() - deprecated[1].length()); - - } else { - if (removed.getEnd() < activeRange.getStart()) { - /* firstIndex < lastIndex < start */ - moveListeners(activeRange, -count); - activeRange = activeRange.offsetBy(-count); - } - /* else: end <= firstIndex, no need to do anything */ - } + public Collection<GridValueChangeListener> getValueChangeListeners() { + return new HashSet<GridValueChangeListener>(activeItemMap.values()); } - /** - * Moves value change listeners in map with given index range by count - */ - private void moveListeners(Range movedRange, int diff) { - if (diff < 0) { - for (Integer i = movedRange.getStart(); i < movedRange.getEnd(); ++i) { - moveListener(i, i + diff); - } - } else if (diff > 0) { - for (Integer i = movedRange.getEnd() - 1; i >= movedRange - .getStart(); --i) { - moveListener(i, i + diff); - } - } else { - // diff == 0 should not happen. If it does, should be no-op - return; - } + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + rowData.put(GridState.JSONKEY_ROWKEY, keyMapper.key(itemId)); } - private void moveListener(Integer oldIndex, Integer newIndex) { - assert valueChangeListeners.get(newIndex) == null : "Overwriting existing listener"; - - GridValueChangeListener listener = valueChangeListeners - .remove(oldIndex); - assert listener != null : "Moving nonexisting listener."; - valueChangeListeners.put(newIndex, listener); - } } /** @@ -601,7 +227,8 @@ public class RpcDataProviderExtension extends AbstractExtension { * @since 7.5.0 * @author Vaadin Ltd */ - public static final class DetailComponentManager implements Serializable { + // TODO this should probably be a static nested class + public final class DetailComponentManager implements DataGenerator { /** * This map represents all the components that have been requested for * each item id. @@ -609,9 +236,8 @@ public class RpcDataProviderExtension extends AbstractExtension { * Normally this map is consistent with what is displayed in the * component hierarchy (and thus the DOM). The only time this map is out * of sync with the DOM is between the any calls to - * {@link #createDetails(Object, int)} or - * {@link #destroyDetails(Object)}, and - * {@link GridClientRpc#setDetailsConnectorChanges(Set)}. + * {@link #createDetails(Object)} or {@link #destroyDetails(Object)}, + * and {@link GridClientRpc#setDetailsConnectorChanges(Set)}. * <p> * This is easily checked: if {@link #unattachedComponents} is * {@link Collection#isEmpty() empty}, then this field is consistent @@ -620,34 +246,11 @@ public class RpcDataProviderExtension extends AbstractExtension { private final Map<Object, Component> visibleDetailsComponents = Maps .newHashMap(); - /** A lookup map for which row contains which details component. */ - private BiMap<Integer, Component> rowIndexToDetails = HashBiMap - .create(); - - /** - * A copy of {@link #rowIndexToDetails} from its last stable state. Used - * for creating a diff against {@link #rowIndexToDetails}. - * - * @see #getAndResetConnectorChanges() - */ - private BiMap<Integer, Component> prevRowIndexToDetails = HashBiMap - .create(); - - /** - * A set keeping track on components that have been created, but not - * attached. They should be attached at some later point in time. - * <p> - * This isn't strictly requried, but it's a handy explicit log. You - * could find out the same thing by taking out all the other components - * and checking whether Grid is their parent or not. - */ - private final Set<Component> unattachedComponents = Sets.newHashSet(); - /** * Keeps tabs on all the details that did not get a component during - * {@link #createDetails(Object, int)}. + * {@link #createDetails(Object)}. */ - private final Map<Object, Integer> emptyDetails = Maps.newHashMap(); + private final Set<Object> emptyDetails = Sets.newHashSet(); private Grid grid; @@ -661,19 +264,16 @@ public class RpcDataProviderExtension extends AbstractExtension { * the item id for which to create the details component. * Assumed not <code>null</code> and that a component is not * currently present for this item previously - * @param rowIndex - * the row index for {@code itemId} * @throws IllegalStateException * if the current details generator provides a component * that was manually attached, or if the same instance has * already been provided */ - public void createDetails(Object itemId, int rowIndex) - throws IllegalStateException { + public void createDetails(Object itemId) throws IllegalStateException { assert itemId != null : "itemId was null"; - Integer newRowIndex = Integer.valueOf(rowIndex); - if (visibleDetailsComponents.containsKey(itemId)) { + if (visibleDetailsComponents.containsKey(itemId) + || emptyDetails.contains(itemId)) { // Don't overwrite existing components return; } @@ -684,58 +284,26 @@ public class RpcDataProviderExtension extends AbstractExtension { DetailsGenerator detailsGenerator = grid.getDetailsGenerator(); Component details = detailsGenerator.getDetails(rowReference); if (details != null) { - String generatorName = detailsGenerator.getClass().getName(); if (details.getParent() != null) { - throw new IllegalStateException(generatorName + String name = detailsGenerator.getClass().getName(); + throw new IllegalStateException(name + " generated a details component that already " - + "was attached. (itemId: " + itemId + ", row: " - + rowIndex + ", component: " + details); - } - - if (rowIndexToDetails.containsValue(details)) { - throw new IllegalStateException(generatorName - + " provided a details component that already " - + "exists in Grid. (itemId: " + itemId + ", row: " - + rowIndex + ", component: " + details); + + "was attached. (itemId: " + itemId + + ", component: " + details + ")"); } visibleDetailsComponents.put(itemId, details); - rowIndexToDetails.put(newRowIndex, details); - unattachedComponents.add(details); - assert !emptyDetails.containsKey(itemId) : "Bookeeping thinks " + details.setParent(grid); + grid.markAsDirty(); + + assert !emptyDetails.contains(itemId) : "Bookeeping thinks " + "itemId is empty even though we just created a " + "component for it (" + itemId + ")"; } else { - assert assertItemIdHasNotMovedAndNothingIsOverwritten(itemId, - newRowIndex); - emptyDetails.put(itemId, newRowIndex); - } - - /* - * Don't attach the components here. It's done by - * GridServerRpc.sendDetailsComponents in a separate roundtrip. - */ - } - - private boolean assertItemIdHasNotMovedAndNothingIsOverwritten( - Object itemId, Integer newRowIndex) { - - Integer oldRowIndex = emptyDetails.get(itemId); - if (!SharedUtil.equals(oldRowIndex, newRowIndex)) { - - assert !emptyDetails.containsKey(itemId) : "Unexpected " - + "change of empty details row index for itemId " - + itemId + " from " + oldRowIndex + " to " - + newRowIndex; - - assert !emptyDetails.containsValue(newRowIndex) : "Bookkeeping" - + " already had another itemId for this empty index " - + "(index: " + newRowIndex + ", new itemId: " + itemId - + ")"; + emptyDetails.add(itemId); } - return true; } /** @@ -756,8 +324,6 @@ public class RpcDataProviderExtension extends AbstractExtension { return; } - rowIndexToDetails.inverse().remove(removedComponent); - removedComponent.setParent(null); grid.markAsDirty(); } @@ -773,81 +339,12 @@ public class RpcDataProviderExtension extends AbstractExtension { public Collection<Component> getComponents() { Set<Component> components = new HashSet<Component>( visibleDetailsComponents.values()); - components.removeAll(unattachedComponents); return components; } - /** - * Gets information on how the connectors have changed. - * <p> - * This method only returns the changes that have been made between two - * calls of this method. I.e. Calling this method once will reset the - * state for the next state. - * <p> - * Used internally by the Grid object. - * - * @return information on how the connectors have changed - */ - public Set<DetailsConnectorChange> getAndResetConnectorChanges() { - Set<DetailsConnectorChange> changes = new HashSet<DetailsConnectorChange>(); - - // populate diff with added/changed - for (Entry<Integer, Component> entry : rowIndexToDetails.entrySet()) { - Component component = entry.getValue(); - assert component != null : "rowIndexToDetails contains a null component"; - - Integer newIndex = entry.getKey(); - Integer oldIndex = prevRowIndexToDetails.inverse().get( - component); - - /* - * only attach components. Detaching already happened in - * destroyDetails. - */ - if (newIndex != null && oldIndex == null) { - assert unattachedComponents.contains(component) : "unattachedComponents does not contain component for index " - + newIndex + " (" + component + ")"; - component.setParent(grid); - unattachedComponents.remove(component); - } - - if (!SharedUtil.equals(oldIndex, newIndex)) { - changes.add(new DetailsConnectorChange(component, oldIndex, - newIndex, emptyDetails.containsKey(component))); - } - } - - // populate diff with removed - for (Entry<Integer, Component> entry : prevRowIndexToDetails - .entrySet()) { - Integer oldIndex = entry.getKey(); - Component component = entry.getValue(); - Integer newIndex = rowIndexToDetails.inverse().get(component); - if (newIndex == null) { - changes.add(new DetailsConnectorChange(null, oldIndex, - null, emptyDetails.containsValue(oldIndex))); - } - } - - // reset diff map - prevRowIndexToDetails = HashBiMap.create(rowIndexToDetails); - - return changes; - } - public void refresh(Object itemId) { - Component component = visibleDetailsComponents.get(itemId); - Integer rowIndex = null; - if (component != null) { - rowIndex = rowIndexToDetails.inverse().get(component); - destroyDetails(itemId); - } else { - rowIndex = emptyDetails.remove(itemId); - } - - assert rowIndex != null : "Given itemId does not map to an " - + "existing detail row (" + itemId + ")"; - createDetails(itemId, rowIndex.intValue()); + destroyDetails(itemId); + createDetails(itemId); } void setGrid(Grid grid) { @@ -856,12 +353,29 @@ public class RpcDataProviderExtension extends AbstractExtension { } this.grid = grid; } + + /** + * {@inheritDoc} + * + * @since 7.6 + */ + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + if (visibleDetails.contains(itemId)) { + // Double check to be sure details component exists. + detailComponentManager.createDetails(itemId); + Component detailsComponent = visibleDetailsComponents + .get(itemId); + rowData.put( + GridState.JSONKEY_DETAILS_VISIBLE, + (detailsComponent != null ? detailsComponent + .getConnectorId() : "")); + } + } } private final Indexed container; - private final ActiveRowHandler activeRowHandler = new ActiveRowHandler(); - private DataProviderRpc rpc; private final ItemSetChangeListener itemListener = new ItemSetChangeListener() { @@ -922,22 +436,10 @@ public class RpcDataProviderExtension extends AbstractExtension { * taking all the corner cases into account. */ - Map<Integer, GridValueChangeListener> listeners = activeRowHandler.valueChangeListeners; - for (GridValueChangeListener listener : listeners.values()) { - listener.removeListener(); - } - - // Wipe clean all details. - HashSet<Object> detailItemIds = new HashSet<Object>( - detailComponentManager.visibleDetailsComponents - .keySet()); - for (Object itemId : detailItemIds) { + for (Object itemId : visibleDetails) { detailComponentManager.destroyDetails(itemId); } - listeners.clear(); - activeRowHandler.activeRange = Range.withLength(0, 0); - /* Mark as dirty to push changes in beforeClientResponse */ bareItemSetTriggeredSizeChange = true; markAsDirty(); @@ -945,16 +447,9 @@ public class RpcDataProviderExtension extends AbstractExtension { } }; - private final DataProviderKeyMapper keyMapper = new DataProviderKeyMapper(); - - private KeyMapper<Object> columnKeys; - /** RpcDataProvider should send the current cache again. */ private boolean refreshCache = false; - private RowReference rowReference; - private CellReference cellReference; - /** Set of updated item ids */ private Set<Object> updatedItemIds = new LinkedHashSet<Object>(); @@ -971,10 +466,15 @@ public class RpcDataProviderExtension extends AbstractExtension { * This map represents all the details that are user-defined as visible. * This does not reflect the status in the DOM. */ - private Set<Object> visibleDetails = new HashSet<Object>(); + // TODO this should probably be inside DetailComponentManager + private final Set<Object> visibleDetails = new HashSet<Object>(); private final DetailComponentManager detailComponentManager = new DetailComponentManager(); + private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>(); + + private final ActiveItemHandler activeItemHandler = new ActiveItemHandler(); + /** * Creates a new data provider using the given container. * @@ -989,22 +489,15 @@ public class RpcDataProviderExtension extends AbstractExtension { @Override public void requestRows(int firstRow, int numberOfRows, int firstCachedRowIndex, int cacheSize) { - pushRowData(firstRow, numberOfRows, firstCachedRowIndex, cacheSize); } @Override - public void setPinned(String key, boolean isPinned) { - Object itemId = keyMapper.getItemId(key); - if (isPinned) { - // Row might already be pinned if it was selected from the - // server - if (!keyMapper.isPinned(itemId)) { - keyMapper.pin(itemId); - } - } else { - keyMapper.unpin(itemId); + public void dropRows(JsonArray rowKeys) { + for (int i = 0; i < rowKeys.length(); ++i) { + activeItemHandler.dropActiveItem(getKeyMapper().get( + rowKeys.getString(i))); } } }); @@ -1014,6 +507,8 @@ public class RpcDataProviderExtension extends AbstractExtension { .addItemSetChangeListener(itemListener); } + addDataGenerator(activeItemHandler); + addDataGenerator(detailComponentManager); } /** @@ -1045,16 +540,11 @@ public class RpcDataProviderExtension extends AbstractExtension { // Send current rows again if needed. if (refreshCache) { - int firstRow = activeRowHandler.activeRange.getStart(); - int numberOfRows = activeRowHandler.activeRange.length(); - - pushRowData(firstRow, numberOfRows, firstRow, numberOfRows); + updatedItemIds.addAll(activeItemHandler.getActiveItemIds()); } } - for (Object itemId : updatedItemIds) { - internalUpdateRowData(itemId); - } + internalUpdateRows(updatedItemIds); // Clear all changes. rowChanges.clear(); @@ -1076,7 +566,6 @@ public class RpcDataProviderExtension extends AbstractExtension { List<?> itemIds = container.getItemIds(fullRange.getStart(), fullRange.length()); - keyMapper.setActiveRows(itemIds); JsonArray rows = Json.createArray(); @@ -1088,83 +577,25 @@ public class RpcDataProviderExtension extends AbstractExtension { for (int i = 0; i < newRange.length() && i + diff < itemIds.size(); ++i) { Object itemId = itemIds.get(i + diff); + rows.set(i, getRowData(getGrid().getColumns(), itemId)); } rpc.setRowData(firstRowToPush, rows); - activeRowHandler.setActiveRows(fullRange); + activeItemHandler.addActiveItems(itemIds); } - private JsonValue getRowData(Collection<Column> columns, Object itemId) { + private JsonObject getRowData(Collection<Column> columns, Object itemId) { Item item = container.getItem(itemId); - JsonObject rowData = Json.createObject(); - - Grid grid = getGrid(); - - for (Column column : columns) { - Object propertyId = column.getPropertyId(); - - Object propertyValue = item.getItemProperty(propertyId).getValue(); - JsonValue encodedValue = encodeValue(propertyValue, - column.getRenderer(), column.getConverter(), - grid.getLocale()); - - rowData.put(columnKeys.key(propertyId), encodedValue); - } - final JsonObject rowObject = Json.createObject(); - rowObject.put(GridState.JSONKEY_DATA, rowData); - rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId)); - - if (visibleDetails.contains(itemId)) { - rowObject.put(GridState.JSONKEY_DETAILS_VISIBLE, true); - } - - rowReference.set(itemId); - - CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator(); - if (cellStyleGenerator != null) { - setGeneratedCellStyles(cellStyleGenerator, rowObject, columns); - } - RowStyleGenerator rowStyleGenerator = grid.getRowStyleGenerator(); - if (rowStyleGenerator != null) { - setGeneratedRowStyles(rowStyleGenerator, rowObject); + for (DataGenerator dg : dataGenerators) { + dg.generateData(itemId, item, rowObject); } return rowObject; } - private void setGeneratedCellStyles(CellStyleGenerator generator, - JsonObject rowObject, Collection<Column> columns) { - JsonObject cellStyles = null; - for (Column column : columns) { - Object propertyId = column.getPropertyId(); - cellReference.set(propertyId); - String style = generator.getStyle(cellReference); - if (style != null && !style.isEmpty()) { - if (cellStyles == null) { - cellStyles = Json.createObject(); - } - - String columnKey = columnKeys.key(propertyId); - cellStyles.put(columnKey, style); - } - } - if (cellStyles != null) { - rowObject.put(GridState.JSONKEY_CELLSTYLES, cellStyles); - } - - } - - private void setGeneratedRowStyles(RowStyleGenerator generator, - JsonObject rowObject) { - String rowStyle = generator.getStyle(rowReference); - if (rowStyle != null && !rowStyle.isEmpty()) { - rowObject.put(GridState.JSONKEY_ROWSTYLE, rowStyle); - } - } - /** * Makes the data source available to the given {@link Grid} component. * @@ -1173,13 +604,38 @@ public class RpcDataProviderExtension extends AbstractExtension { * @param columnKeys * the key mapper for columns */ - public void extend(Grid component, KeyMapper<Object> columnKeys) { - this.columnKeys = columnKeys; + public void extend(Grid component) { detailComponentManager.setGrid(component); super.extend(component); } /** + * Adds a {@link DataGenerator} for this {@code RpcDataProviderExtension}. + * DataGenerators are called when sending row data to client. If given + * DataGenerator is already added, this method does nothing. + * + * @since 7.6 + * @param generator + * generator to add + */ + public void addDataGenerator(DataGenerator generator) { + dataGenerators.add(generator); + } + + /** + * Removes a {@link DataGenerator} from this + * {@code RpcDataProviderExtension}. If given DataGenerator is not added to + * this data provider, this method does nothing. + * + * @since 7.6 + * @param generator + * generator to remove + */ + public void removeDataGenerator(DataGenerator generator) { + dataGenerators.remove(generator); + } + + /** * Informs the client side that new rows have been inserted into the data * source. * @@ -1205,8 +661,6 @@ public class RpcDataProviderExtension extends AbstractExtension { rpc.insertRowData(index, count); } }); - - activeRowHandler.insertRows(index, count); } /** @@ -1231,8 +685,6 @@ public class RpcDataProviderExtension extends AbstractExtension { rpc.removeRowData(index, count); } }); - - activeRowHandler.removeRows(index, count); } /** @@ -1252,18 +704,20 @@ public class RpcDataProviderExtension extends AbstractExtension { updatedItemIds.add(itemId); } - private void internalUpdateRowData(Object itemId) { - int index = container.indexOfId(itemId); - if (index >= 0) { - JsonValue row = getRowData(getGrid().getColumns(), itemId); - JsonArray rowArray = Json.createArray(); - rowArray.set(0, row); - rpc.setRowData(index, rowArray); + private void internalUpdateRows(Set<Object> itemIds) { + if (itemIds.isEmpty()) { + return; + } - if (isDetailsVisible(itemId)) { - detailComponentManager.createDetails(itemId, index); + JsonArray rowData = Json.createArray(); + int i = 0; + for (Object itemId : itemIds) { + if (activeItemHandler.getActiveItemIds().contains(itemId)) { + JsonObject row = getRowData(getGrid().getColumns(), itemId); + rowData.set(i++, row); } } + rpc.updateRowData(rowData); } /** @@ -1280,20 +734,15 @@ public class RpcDataProviderExtension extends AbstractExtension { public void setParent(ClientConnector parent) { if (parent == null) { // We're being detached, release various listeners - - activeRowHandler - .removeValueChangeListeners(activeRowHandler.activeRange); + activeItemHandler.internalDropActiveItems(activeItemHandler + .getActiveItemIds()); if (container instanceof ItemSetChangeNotifier) { ((ItemSetChangeNotifier) container) .removeItemSetChangeListener(itemListener); } - } else if (parent instanceof Grid) { - Grid grid = (Grid) parent; - rowReference = new RowReference(grid); - cellReference = new CellReference(rowReference); - } else { + } else if (!(parent instanceof Grid)) { throw new IllegalStateException( "Grid is the only accepted parent type"); } @@ -1308,7 +757,13 @@ public class RpcDataProviderExtension extends AbstractExtension { * a list of removed columns */ public void columnsRemoved(List<Column> removedColumns) { - activeRowHandler.columnsRemoved(removedColumns); + for (GridValueChangeListener l : activeItemHandler + .getValueChangeListeners()) { + l.removeColumns(removedColumns); + } + + // No need to resend unchanged data. Client will remember the old + // columns until next set of rows is sent. } /** @@ -1318,11 +773,17 @@ public class RpcDataProviderExtension extends AbstractExtension { * a list of added columns */ public void columnsAdded(List<Column> addedColumns) { - activeRowHandler.columnsAdded(addedColumns); + for (GridValueChangeListener l : activeItemHandler + .getValueChangeListeners()) { + l.addColumns(addedColumns); + } + + // Resend all rows to contain new data. + refreshCache(); } - public DataProviderKeyMapper getKeyMapper() { - return keyMapper; + public KeyMapper<Object> getKeyMapper() { + return activeItemHandler.keyMapper; } protected Grid getGrid() { @@ -1330,62 +791,6 @@ public class RpcDataProviderExtension extends AbstractExtension { } /** - * Converts and encodes the given data model property value using the given - * converter and renderer. This method is public only for testing purposes. - * - * @param renderer - * the renderer to use - * @param converter - * the converter to use - * @param modelValue - * the value to convert and encode - * @param locale - * the locale to use in conversion - * @return an encoded value ready to be sent to the client - */ - public static <T> JsonValue encodeValue(Object modelValue, - Renderer<T> renderer, Converter<?, ?> converter, Locale locale) { - Class<T> presentationType = renderer.getPresentationType(); - T presentationValue; - - if (converter == null) { - try { - presentationValue = presentationType.cast(modelValue); - } catch (ClassCastException e) { - if (presentationType == String.class) { - // If there is no converter, just fallback to using - // toString(). - // modelValue can't be null as Class.cast(null) will always - // succeed - presentationValue = (T) modelValue.toString(); - } else { - throw new Converter.ConversionException( - "Unable to convert value of type " - + modelValue.getClass().getName() - + " to presentation type " - + presentationType.getName() - + ". No converter is set and the types are not compatible."); - } - } - } else { - assert presentationType.isAssignableFrom(converter - .getPresentationType()); - @SuppressWarnings("unchecked") - Converter<T, Object> safeConverter = (Converter<T, Object>) converter; - presentationValue = safeConverter.convertToPresentation(modelValue, - safeConverter.getPresentationType(), locale); - } - - JsonValue encodedValue = renderer.encode(presentationValue); - - return encodedValue; - } - - private static Logger getLogger() { - return Logger.getLogger(RpcDataProviderExtension.class.getName()); - } - - /** * Marks a row's details to be visible or hidden. * <p> * If that row is currently in the client side's cache, this information @@ -1399,37 +804,21 @@ public class RpcDataProviderExtension extends AbstractExtension { * hide */ public void setDetailsVisible(Object itemId, boolean visible) { - final boolean modified; - if (visible) { - modified = visibleDetails.add(itemId); + visibleDetails.add(itemId); /* - * We don't want to create the component here, since the component - * might be out of view, and thus we don't know where the details - * should end up on the client side. This is also a great thing to - * optimize away, so that in case a lot of things would be opened at - * once, a huge chunk of data doesn't get sent over immediately. + * This might be an issue with a huge number of open rows, but as of + * now this works in most of the cases. */ - + detailComponentManager.createDetails(itemId); } else { - modified = visibleDetails.remove(itemId); + visibleDetails.remove(itemId); - /* - * Here we can try to destroy the component no matter what. The - * component has been removed and should be detached from the - * component hierarchy. The details row will be closed on the client - * side automatically. - */ detailComponentManager.destroyDetails(itemId); } - int rowIndex = indexOf(itemId); - boolean modifiedRowIsActive = activeRowHandler.activeRange - .contains(rowIndex); - if (modified && modifiedRowIsActive) { - updateRowData(itemId); - } + updateRowData(itemId); } /** @@ -1454,18 +843,10 @@ public class RpcDataProviderExtension extends AbstractExtension { public void refreshDetails() { for (Object itemId : ImmutableSet.copyOf(visibleDetails)) { detailComponentManager.refresh(itemId); + updateRowData(itemId); } } - private int indexOf(Object itemId) { - /* - * It would be great if we could optimize this method away, since the - * normal usage of Grid doesn't need any indices to be known. It was - * already optimized away once, maybe we can do away with these as well. - */ - return container.indexOfId(itemId); - } - /** * Gets the detail component manager for this data provider * @@ -1475,13 +856,4 @@ public class RpcDataProviderExtension extends AbstractExtension { public DetailComponentManager getDetailComponentManager() { return detailComponentManager; } - - @Override - public void detach() { - for (Object itemId : ImmutableSet.copyOf(visibleDetails)) { - detailComponentManager.destroyDetails(itemId); - } - - super.detach(); - } } diff --git a/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java b/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java index 4bb4e4c1b2..4329219e96 100644 --- a/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java +++ b/server/src/com/vaadin/data/util/ContainerOrderedWrapper.java @@ -51,12 +51,14 @@ public class ContainerOrderedWrapper implements Container.Ordered, private final Container container; /** - * Ordering information, ie. the mapping from Item ID to the next item ID + * Ordering information, ie. the mapping from Item ID to the next item ID. + * The last item id should not be present */ private Hashtable<Object, Object> next; /** - * Reverse ordering information for convenience and performance reasons. + * Reverse ordering information for convenience and performance reasons. The + * first item id should not be present */ private Hashtable<Object, Object> prev; @@ -124,13 +126,21 @@ public class ContainerOrderedWrapper implements Container.Ordered, first = nid; } if (last.equals(id)) { - first = pid; + last = pid; } if (nid != null) { - prev.put(nid, pid); + if (pid == null) { + prev.remove(nid); + } else { + prev.put(nid, pid); + } } if (pid != null) { - next.put(pid, nid); + if (nid == null) { + next.remove(pid); + } else { + next.put(pid, nid); + } } next.remove(id); prev.remove(id); @@ -200,7 +210,7 @@ public class ContainerOrderedWrapper implements Container.Ordered, final Collection<?> ids = container.getItemIds(); // Recreates ordering if some parts of it are missing - if (next == null || first == null || last == null || prev != null) { + if (next == null || first == null || last == null || prev == null) { first = null; last = null; next = new Hashtable<Object, Object>(); @@ -219,7 +229,7 @@ public class ContainerOrderedWrapper implements Container.Ordered, // Adds missing items for (final Iterator<?> i = ids.iterator(); i.hasNext();) { final Object id = i.next(); - if (!next.containsKey(id)) { + if (!next.containsKey(id) && last != id) { addToOrderWrapper(id); } } diff --git a/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java b/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java index 0e802da879..f965cfcc6a 100644 --- a/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java +++ b/server/src/com/vaadin/data/util/converter/StringToBooleanConverter.java @@ -19,20 +19,43 @@ package com.vaadin.data.util.converter; import java.util.Locale; /** - * A converter that converts from {@link String} to {@link Boolean} and back. - * The String representation is given by Boolean.toString(). - * <p> - * Leading and trailing white spaces are ignored when converting from a String. - * </p> - * + * A converter that converts from {@link String} to {@link Boolean} and back. The String representation is given by + * {@link Boolean#toString()} or provided in constructor {@link #StringToBooleanConverter(String, String)}. + * <p> Leading and trailing white spaces are ignored when converting from a String. </p> + * <p> For language-dependent representation, subclasses should overwrite {@link #getFalseString(Locale)} and {@link #getTrueString(Locale)}</p> + * * @author Vaadin Ltd * @since 7.0 */ public class StringToBooleanConverter implements Converter<String, Boolean> { + private final String trueString; + + private final String falseString; + + /** + * Creates converter with default string representations - "true" and "false" + * + */ + public StringToBooleanConverter() { + this(Boolean.TRUE.toString(), Boolean.FALSE.toString()); + } + + /** + * Creates converter with custom string representation. + * + * @since 7.5.4 + * @param falseString string representation for <code>false</code> + * @param trueString string representation for <code>true</code> + */ + public StringToBooleanConverter(String trueString, String falseString) { + this.trueString = trueString; + this.falseString = falseString; + } + /* * (non-Javadoc) - * + * * @see * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, * java.lang.Class, java.util.Locale) @@ -59,26 +82,26 @@ public class StringToBooleanConverter implements Converter<String, Boolean> { } /** - * Gets the string representation for true. Default is "true". - * + * Gets the string representation for true. Default is "true", if not set in constructor. + * * @return the string representation for true */ protected String getTrueString() { - return Boolean.TRUE.toString(); + return trueString; } /** - * Gets the string representation for false. Default is "false". - * + * Gets the string representation for false. Default is "false", if not set in constructor. + * * @return the string representation for false */ protected String getFalseString() { - return Boolean.FALSE.toString(); + return falseString; } /* * (non-Javadoc) - * + * * @see * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang * .Object, java.lang.Class, java.util.Locale) @@ -91,15 +114,39 @@ public class StringToBooleanConverter implements Converter<String, Boolean> { return null; } if (value) { - return getTrueString(); + return getTrueString(locale); } else { - return getFalseString(); + return getFalseString(locale); } } + /** + * Gets the locale-depended string representation for false. + * Default is locale-independent value provided by {@link #getFalseString()} + * + * @since 7.5.4 + * @param locale to be used + * @return the string representation for false + */ + protected String getFalseString(Locale locale) { + return getFalseString(); + } + + /** + * Gets the locale-depended string representation for true. + * Default is locale-independent value provided by {@link #getTrueString()} + * + * @since 7.5.4 + * @param locale to be used + * @return the string representation for true + */ + protected String getTrueString(Locale locale) { + return getTrueString(); + } + /* * (non-Javadoc) - * + * * @see com.vaadin.data.util.converter.Converter#getModelType() */ @Override @@ -109,7 +156,7 @@ public class StringToBooleanConverter implements Converter<String, Boolean> { /* * (non-Javadoc) - * + * * @see com.vaadin.data.util.converter.Converter#getPresentationType() */ @Override diff --git a/server/src/com/vaadin/event/ShortcutAction.java b/server/src/com/vaadin/event/ShortcutAction.java index 09accae1c7..dd511c23c0 100644 --- a/server/src/com/vaadin/event/ShortcutAction.java +++ b/server/src/com/vaadin/event/ShortcutAction.java @@ -17,6 +17,7 @@ package com.vaadin.event; import java.io.Serializable; +import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -55,7 +56,7 @@ public class ShortcutAction extends Action { private final int keyCode; - private final int[] modifiers; + private int[] modifiers; /** * Creates a shortcut that reacts to the given {@link KeyCode} and @@ -73,7 +74,7 @@ public class ShortcutAction extends Action { public ShortcutAction(String caption, int kc, int... m) { super(caption); keyCode = kc; - modifiers = m; + setModifiers(m); } /** @@ -94,7 +95,7 @@ public class ShortcutAction extends Action { public ShortcutAction(String caption, Resource icon, int kc, int... m) { super(caption, icon); keyCode = kc; - modifiers = m; + setModifiers(m); } /** @@ -190,7 +191,7 @@ public class ShortcutAction extends Action { // Given modifiers override this indicated in the caption if (modifierKeys != null) { - modifiers = modifierKeys; + setModifiers(modifierKeys); } else { // Read modifiers from caption int[] mod = new int[match.length() - 1]; @@ -208,13 +209,30 @@ public class ShortcutAction extends Action { break; } } - modifiers = mod; + setModifiers(mod); } } else { keyCode = -1; - modifiers = modifierKeys; + setModifiers(modifierKeys); } + + } + + /** + * When setting modifiers, make sure that modifiers is a valid array AND + * that it's sorted. + * + * @param modifiers + * the modifier keys for this shortcut + */ + private void setModifiers(int... modifiers) { + if (modifiers == null) { + this.modifiers = new int[0]; + } else { + this.modifiers = modifiers; + } + Arrays.sort(this.modifiers); } /** diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java index 5a0d852299..77a1a3134e 100644 --- a/server/src/com/vaadin/server/Constants.java +++ b/server/src/com/vaadin/server/Constants.java @@ -137,6 +137,7 @@ public interface Constants { static final String SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING = "legacyPropertyToString"; static final String SERVLET_PARAMETER_SYNC_ID_CHECK = "syncIdCheck"; static final String SERVLET_PARAMETER_SENDURLSASPARAMETERS = "sendUrlsAsParameters"; + static final String SERVLET_PARAMETER_PUSH_SUSPEND_TIMEOUT_LONGPOLLING = "pushLongPollingSuspendTimeout"; // Configurable parameter names static final String PARAMETER_VAADIN_RESOURCES = "Resources"; diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index 7aada2402d..61df02feaa 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -28,6 +28,7 @@ import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -694,17 +695,20 @@ public class VaadinServlet extends HttpServlet implements Constants { return false; } + String decodedRequestURI = URLDecoder.decode(request.getRequestURI(), + "UTF-8"); if ((request.getContextPath() != null) - && (request.getRequestURI().startsWith("/VAADIN/"))) { - serveStaticResourcesInVAADIN(request.getRequestURI(), request, - response); + && (decodedRequestURI.startsWith("/VAADIN/"))) { + serveStaticResourcesInVAADIN(decodedRequestURI, request, response); return true; - } else if (request.getRequestURI().startsWith( - request.getContextPath() + "/VAADIN/")) { + } + + String decodedContextPath = URLDecoder.decode(request.getContextPath(), + "UTF-8"); + if (decodedRequestURI.startsWith(decodedContextPath + "/VAADIN/")) { serveStaticResourcesInVAADIN( - request.getRequestURI().substring( - request.getContextPath().length()), request, - response); + decodedRequestURI.substring(decodedContextPath.length()), + request, response); return true; } diff --git a/server/src/com/vaadin/server/WebBrowser.java b/server/src/com/vaadin/server/WebBrowser.java index 66018b02f2..9bf30cb3db 100644 --- a/server/src/com/vaadin/server/WebBrowser.java +++ b/server/src/com/vaadin/server/WebBrowser.java @@ -126,6 +126,20 @@ public class WebBrowser implements Serializable { } /** + * Tests whether the user is using Edge. + * + * @return true if the user is using Edge, false if the user is not using + * Edge or if no information on the browser is present + */ + public boolean isEdge() { + if (browserDetails == null) { + return false; + } + + return browserDetails.isEdge(); + } + + /** * Tests whether the user is using Safari. * * @return true if the user is using Safari, false if the user is not using diff --git a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java index 87ce9ba81a..5c0d2e14d4 100644 --- a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java +++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java @@ -26,7 +26,9 @@ import java.io.Writer; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.logging.ConsoleHandler; import java.util.logging.Level; +import java.util.logging.LogRecord; import java.util.logging.Logger; import org.atmosphere.cpr.AtmosphereResource; @@ -274,6 +276,13 @@ public class AtmospherePushConnection implements PushConnection { public void disconnect() { assert isConnected(); + if (resource == null) { + // Already disconnected. Should not happen but if it does, we don't + // want to cause NPEs + getLogger() + .fine("AtmospherePushConnection.disconnect() called twice, this should not happen"); + return; + } if (resource.isResumed()) { // This can happen for long polling because of // http://dev.vaadin.com/ticket/16919 @@ -345,4 +354,32 @@ public class AtmospherePushConnection implements PushConnection { private static Logger getLogger() { return Logger.getLogger(AtmospherePushConnection.class.getName()); } + + /** + * Internal method used for reconfiguring loggers to show all Atmosphere log + * messages in the console. + * + * @since 7.6 + */ + public static void enableAtmosphereDebugLogging() { + Level level = Level.FINEST; + + Logger atmosphereLogger = Logger.getLogger("org.atmosphere"); + if (atmosphereLogger.getLevel() == level) { + // Already enabled + return; + } + + atmosphereLogger.setLevel(level); + + // Without this logging, we will have a ClassCircularityError + LogRecord record = new LogRecord(Level.INFO, + "Enabling Atmosphere debug logging"); + atmosphereLogger.log(record); + + ConsoleHandler ch = new ConsoleHandler(); + ch.setLevel(Level.ALL); + atmosphereLogger.addHandler(ch); + } + } diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java index 576cbd8411..532c7fe95b 100644 --- a/server/src/com/vaadin/server/communication/FileUploadHandler.java +++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java @@ -269,7 +269,7 @@ public class FileUploadHandler implements RequestHandler { streamVariable = uI.getConnectorTracker().getStreamVariable( connectorId, variableName); String secKey = uI.getConnectorTracker().getSeckey(streamVariable); - if (!secKey.equals(parts[3])) { + if (secKey == null || !secKey.equals(parts[3])) { // TODO Should rethink error handling return true; } diff --git a/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java b/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java index f36d403dd5..6d2843a4fc 100644 --- a/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java +++ b/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java @@ -181,8 +181,13 @@ public class JSR356WebsocketInitializer implements ServletContextListener { */ protected boolean isVaadinServlet(ServletRegistration servletRegistration) { try { - Class<?> servletClass = Class.forName(servletRegistration - .getClassName()); + String servletClassName = servletRegistration.getClassName(); + if (servletClassName.equals("com.ibm.ws.wsoc.WsocServlet")) { + // Websphere servlet which implements websocket endpoints, + // dynamically added + return false; + } + Class<?> servletClass = Class.forName(servletClassName); return VaadinServlet.class.isAssignableFrom(servletClass); } catch (Exception e) { // This will fail in OSGi environments, assume everything is a diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java index 01077c3f86..994415a0b4 100644 --- a/server/src/com/vaadin/server/communication/PushHandler.java +++ b/server/src/com/vaadin/server/communication/PushHandler.java @@ -55,6 +55,8 @@ import elemental.json.JsonException; */ public class PushHandler { + private int longPollingSuspendTimeout = -1; + /** * Callback interface used internally to process an event with the * corresponding UI properly locked. @@ -107,7 +109,7 @@ public class PushHandler { return; } - resource.suspend(); + suspend(resource); AtmospherePushConnection connection = getConnectionForUI(ui); assert (connection != null); @@ -174,6 +176,21 @@ public class PushHandler { } /** + * Suspends the given resource + * + * @since + * @param resource + * the resource to suspend + */ + protected void suspend(AtmosphereResource resource) { + if (resource.transport() == TRANSPORT.LONG_POLLING) { + resource.suspend(getLongPollingSuspendTimeout()); + } else { + resource.suspend(-1); + } + } + + /** * Find the UI for the atmosphere resource, lock it and invoke the callback. * * @param resource @@ -493,4 +510,26 @@ public class PushHandler { resource.transport() == TRANSPORT.WEBSOCKET); } + /** + * Sets the timeout used for suspend calls when using long polling. + * + * If you are using a proxy with a defined idle timeout, set the suspend + * timeout to a value smaller than the proxy timeout so that the server is + * aware of a reconnect taking place. + * + * @param suspendTimeout + * the timeout to use for suspended AtmosphereResources + */ + public void setLongPollingSuspendTimeout(int longPollingSuspendTimeout) { + this.longPollingSuspendTimeout = longPollingSuspendTimeout; + } + + /** + * Gets the timeout used for suspend calls when using long polling. + * + * @return the timeout to use for suspended AtmosphereResources + */ + public int getLongPollingSuspendTimeout() { + return longPollingSuspendTimeout; + } } diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java index c01c74e5cd..c44fcd9ef3 100644 --- a/server/src/com/vaadin/server/communication/PushRequestHandler.java +++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java @@ -77,7 +77,7 @@ public class PushRequestHandler implements RequestHandler, final ServletConfig vaadinServletConfig = service.getServlet() .getServletConfig(); - pushHandler = new PushHandler(service); + pushHandler = createPushHandler(service); atmosphere = getPreInitializedAtmosphere(vaadinServletConfig); if (atmosphere == null) { @@ -100,7 +100,12 @@ public class PushRequestHandler implements RequestHandler, "Using pre-initialized Atmosphere for servlet " + vaadinServletConfig.getServletName()); } - + pushHandler + .setLongPollingSuspendTimeout(atmosphere + .getAtmosphereConfig() + .getInitParameter( + com.vaadin.server.Constants.SERVLET_PARAMETER_PUSH_SUSPEND_TIMEOUT_LONGPOLLING, + -1)); for (AtmosphereHandlerWrapper handlerWrapper : atmosphere .getAtmosphereHandlers().values()) { AtmosphereHandler handler = handlerWrapper.atmosphereHandler; @@ -113,6 +118,22 @@ public class PushRequestHandler implements RequestHandler, } } + /** + * Creates a push handler for this request handler. + * <p> + * Create your own request handler and override this method if you want to + * customize the {@link PushHandler}, e.g. to dynamically decide the suspend + * timeout. + * + * @since + * @param service + * the vaadin service + * @return the push handler to use for this service + */ + protected PushHandler createPushHandler(VaadinServletService service) { + return new PushHandler(service); + } + private static final Logger getLogger() { return Logger.getLogger(PushRequestHandler.class.getName()); } diff --git a/server/src/com/vaadin/ui/AbstractFocusable.java b/server/src/com/vaadin/ui/AbstractFocusable.java new file mode 100644 index 0000000000..ad3b96f29b --- /dev/null +++ b/server/src/com/vaadin/ui/AbstractFocusable.java @@ -0,0 +1,134 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui; + +import com.vaadin.event.FieldEvents.BlurEvent; +import com.vaadin.event.FieldEvents.BlurListener; +import com.vaadin.event.FieldEvents.BlurNotifier; +import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; +import com.vaadin.event.FieldEvents.FocusEvent; +import com.vaadin.event.FieldEvents.FocusListener; +import com.vaadin.event.FieldEvents.FocusNotifier; +import com.vaadin.shared.ui.TabIndexState; +import com.vaadin.ui.Component.Focusable; + +/** + * An abstract base class for focusable components. Includes API for setting the + * tab index, programmatic focusing, and adding focus and blur listeners. + * + * @since 7.6 + * @author Vaadin Ltd + */ +public abstract class AbstractFocusable extends AbstractComponent implements + Focusable, FocusNotifier, BlurNotifier { + + protected AbstractFocusable() { + registerRpc(new FocusAndBlurServerRpcImpl(this) { + @Override + protected void fireEvent(Event event) { + AbstractFocusable.this.fireEvent(event); + } + }); + } + + @Override + public void addBlurListener(BlurListener listener) { + addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener, + BlurListener.blurMethod); + } + + /** + * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)} + */ + @Override + @Deprecated + public void addListener(BlurListener listener) { + addBlurListener(listener); + } + + @Override + public void removeBlurListener(BlurListener listener) { + removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #removeBlurListener(BlurListener)} + */ + @Override + @Deprecated + public void removeListener(BlurListener listener) { + removeBlurListener(listener); + + } + + @Override + public void addFocusListener(FocusListener listener) { + addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener, + FocusListener.focusMethod); + + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #addFocusListener(FocusListener)} + */ + @Override + @Deprecated + public void addListener(FocusListener listener) { + addFocusListener(listener); + } + + @Override + public void removeFocusListener(FocusListener listener) { + removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener); + } + + /** + * @deprecated As of 7.0, replaced by + * {@link #removeFocusListener(FocusListener)} + */ + @Override + @Deprecated + public void removeListener(FocusListener listener) { + removeFocusListener(listener); + } + + @Override + public void focus() { + super.focus(); + } + + @Override + public int getTabIndex() { + return getState(false).tabIndex; + } + + @Override + public void setTabIndex(int tabIndex) { + getState().tabIndex = tabIndex; + } + + @Override + protected TabIndexState getState() { + return (TabIndexState) super.getState(); + } + + @Override + protected TabIndexState getState(boolean markAsDirty) { + return (TabIndexState) super.getState(markAsDirty); + } +} diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java index 6beb6ed686..a918780a60 100644 --- a/server/src/com/vaadin/ui/Button.java +++ b/server/src/com/vaadin/ui/Button.java @@ -24,12 +24,7 @@ import org.jsoup.nodes.Attributes; import org.jsoup.nodes.Element; import com.vaadin.event.Action; -import com.vaadin.event.FieldEvents; -import com.vaadin.event.FieldEvents.BlurEvent; -import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl; -import com.vaadin.event.FieldEvents.FocusEvent; -import com.vaadin.event.FieldEvents.FocusListener; import com.vaadin.event.ShortcutAction; import com.vaadin.event.ShortcutAction.KeyCode; import com.vaadin.event.ShortcutAction.ModifierKey; @@ -38,7 +33,6 @@ import com.vaadin.server.Resource; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.ui.button.ButtonServerRpc; import com.vaadin.shared.ui.button.ButtonState; -import com.vaadin.ui.Component.Focusable; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.util.ReflectTools; @@ -50,8 +44,7 @@ import com.vaadin.util.ReflectTools; * @since 3.0 */ @SuppressWarnings("serial") -public class Button extends AbstractComponent implements - FieldEvents.BlurNotifier, FieldEvents.FocusNotifier, Focusable, +public class Button extends AbstractFocusable implements Action.ShortcutNotifier { private ButtonServerRpc rpc = new ButtonServerRpc() { @@ -72,20 +65,11 @@ public class Button extends AbstractComponent implements } }; - FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(this) { - - @Override - protected void fireEvent(Event event) { - Button.this.fireEvent(event); - } - }; - /** * Creates a new push button. */ public Button() { registerRpc(rpc); - registerRpc(focusBlurRpc); } /** @@ -393,67 +377,6 @@ public class Button extends AbstractComponent implements fireEvent(new Button.ClickEvent(this, details)); } - @Override - public void addBlurListener(BlurListener listener) { - addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener, - BlurListener.blurMethod); - } - - /** - * @deprecated As of 7.0, replaced by {@link #addBlurListener(BlurListener)} - **/ - @Override - @Deprecated - public void addListener(BlurListener listener) { - addBlurListener(listener); - } - - @Override - public void removeBlurListener(BlurListener listener) { - removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeBlurListener(BlurListener)} - **/ - @Override - @Deprecated - public void removeListener(BlurListener listener) { - removeBlurListener(listener); - } - - @Override - public void addFocusListener(FocusListener listener) { - addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener, - FocusListener.focusMethod); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #addFocusListener(FocusListener)} - **/ - @Override - @Deprecated - public void addListener(FocusListener listener) { - addFocusListener(listener); - } - - @Override - public void removeFocusListener(FocusListener listener) { - removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener); - } - - /** - * @deprecated As of 7.0, replaced by - * {@link #removeFocusListener(FocusListener)} - **/ - @Override - @Deprecated - public void removeListener(FocusListener listener) { - removeFocusListener(listener); - } - /* * Actions */ @@ -575,32 +498,6 @@ public class Button extends AbstractComponent implements getState().disableOnClick = disableOnClick; } - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Component.Focusable#getTabIndex() - */ - @Override - public int getTabIndex() { - return getState(false).tabIndex; - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.Component.Focusable#setTabIndex(int) - */ - @Override - public void setTabIndex(int tabIndex) { - getState().tabIndex = tabIndex; - } - - @Override - public void focus() { - // Overridden only to make public - super.focus(); - } - @Override protected ButtonState getState() { return (ButtonState) super.getState(); diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index e9469c5bca..215df3a6a3 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -31,6 +31,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -45,14 +46,17 @@ import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.google.gwt.thirdparty.guava.common.collect.Sets.SetView; import com.vaadin.data.Container; import com.vaadin.data.Container.Indexed; +import com.vaadin.data.Container.ItemSetChangeEvent; +import com.vaadin.data.Container.ItemSetChangeListener; +import com.vaadin.data.Container.ItemSetChangeNotifier; import com.vaadin.data.Container.PropertySetChangeEvent; import com.vaadin.data.Container.PropertySetChangeListener; import com.vaadin.data.Container.PropertySetChangeNotifier; import com.vaadin.data.Container.Sortable; +import com.vaadin.data.DataGenerator; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.RpcDataProviderExtension; -import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper; import com.vaadin.data.RpcDataProviderExtension.DetailComponentManager; import com.vaadin.data.Validator.InvalidValueException; import com.vaadin.data.fieldgroup.DefaultFieldGroupFieldFactory; @@ -77,6 +81,7 @@ import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.AbstractExtension; import com.vaadin.server.EncodeResult; import com.vaadin.server.ErrorMessage; +import com.vaadin.server.Extension; import com.vaadin.server.JsonCodec; import com.vaadin.server.KeyMapper; import com.vaadin.server.VaadinSession; @@ -89,13 +94,16 @@ import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridConstants; import com.vaadin.shared.ui.grid.GridServerRpc; import com.vaadin.shared.ui.grid.GridState; -import com.vaadin.shared.ui.grid.GridState.SharedSelectionMode; import com.vaadin.shared.ui.grid.GridStaticCellType; import com.vaadin.shared.ui.grid.GridStaticSectionState; import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState; import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState; import com.vaadin.shared.ui.grid.HeightMode; import com.vaadin.shared.ui.grid.ScrollDestination; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc; +import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState; import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; @@ -106,7 +114,6 @@ import com.vaadin.ui.renderers.TextRenderer; import com.vaadin.util.ReflectTools; import elemental.json.Json; -import elemental.json.JsonArray; import elemental.json.JsonObject; import elemental.json.JsonValue; @@ -172,7 +179,7 @@ import elemental.json.JsonValue; * @since 7.4 * @author Vaadin Ltd */ -public class Grid extends AbstractComponent implements SelectionNotifier, +public class Grid extends AbstractFocusable implements SelectionNotifier, SortNotifier, SelectiveRenderer, ItemClickNotifier { /** @@ -511,6 +518,100 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** + * Interface for an editor event listener + */ + public interface EditorListener extends Serializable { + + public static final Method EDITOR_OPEN_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorOpened", + EditorOpenEvent.class); + public static final Method EDITOR_MOVE_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorMoved", + EditorMoveEvent.class); + public static final Method EDITOR_CLOSE_METHOD = ReflectTools + .findMethod(EditorListener.class, "editorClosed", + EditorCloseEvent.class); + + /** + * Called when an editor is opened + * + * @param e + * an editor open event object + */ + public void editorOpened(EditorOpenEvent e); + + /** + * Called when an editor is reopened without closing it first + * + * @param e + * an editor move event object + */ + public void editorMoved(EditorMoveEvent e); + + /** + * Called when an editor is closed + * + * @param e + * an editor close event object + */ + public void editorClosed(EditorCloseEvent e); + + } + + /** + * Base class for editor related events + */ + public static abstract class EditorEvent extends Component.Event { + + private Object itemID; + + protected EditorEvent(Grid source, Object itemID) { + super(source); + this.itemID = itemID; + } + + /** + * Get the item (row) for which this editor was opened + */ + public Object getItem() { + return itemID; + } + + } + + /** + * This event gets fired when an editor is opened + */ + public static class EditorOpenEvent extends EditorEvent { + + public EditorOpenEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** + * This event gets fired when an editor is opened while another row is being + * edited (i.e. editor focus moves elsewhere) + */ + public static class EditorMoveEvent extends EditorEvent { + + public EditorMoveEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** + * This event gets fired when an editor is dismissed or closed by other + * means. + */ + public static class EditorCloseEvent extends EditorEvent { + + public EditorCloseEvent(Grid source, Object itemID) { + super(source, itemID); + } + } + + /** * Default error handler for the editor * */ @@ -611,8 +712,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * The server-side interface that controls Grid's selection state. + * SelectionModel should extend {@link AbstractGridExtension}. */ - public interface SelectionModel extends Serializable { + public interface SelectionModel extends Serializable, Extension { /** * Checks whether an item is selected or not. * @@ -631,6 +733,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Injects the current {@link Grid} instance into the SelectionModel. + * This method should usually call the extend method of + * {@link AbstractExtension}. * <p> * <em>Note:</em> This method should not be called manually. * @@ -872,10 +976,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * A base class for SelectionModels that contains some of the logic that is * reusable. */ - public static abstract class AbstractSelectionModel implements - SelectionModel { + public static abstract class AbstractSelectionModel extends + AbstractGridExtension implements SelectionModel, DataGenerator { protected final LinkedHashSet<Object> selection = new LinkedHashSet<Object>(); - protected Grid grid = null; @Override public boolean isSelected(final Object itemId) { @@ -889,7 +992,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, @Override public void setGrid(final Grid grid) { - this.grid = grid; + if (grid != null) { + extend(grid); + } } /** @@ -903,7 +1008,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ protected void checkItemIdExists(Object itemId) throws IllegalArgumentException { - if (!grid.getContainerDataSource().containsId(itemId)) { + if (!getParentGrid().getContainerDataSource().containsId(itemId)) { throw new IllegalArgumentException("Given item id (" + itemId + ") does not exist in the container"); } @@ -945,7 +1050,19 @@ public class Grid extends AbstractComponent implements SelectionNotifier, protected void fireSelectionEvent( final Collection<Object> oldSelection, final Collection<Object> newSelection) { - grid.fireSelectionEvent(oldSelection, newSelection); + getParentGrid().fireSelectionEvent(oldSelection, newSelection); + } + + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + if (isSelected(itemId)) { + rowData.put(GridState.JSONKEY_SELECTED, true); + } + } + + @Override + protected Object getItemId(String rowKey) { + return rowKey != null ? super.getItemId(rowKey) : null; } } @@ -954,8 +1071,25 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ public static class SingleSelectionModel extends AbstractSelectionModel implements SelectionModel.Single { + + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + registerRpc(new SingleSelectionModelServerRpc() { + + @Override + public void select(String rowKey) { + SingleSelectionModel.this.select(getItemId(rowKey), false); + } + }); + } + @Override public boolean select(final Object itemId) { + return select(itemId, true); + } + + protected boolean select(final Object itemId, boolean refresh) { if (itemId == null) { return deselect(getSelectedRow()); } @@ -967,7 +1101,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, if (modified) { final Collection<Object> deselected; if (selectedRow != null) { - deselectInternal(selectedRow, false); + deselectInternal(selectedRow, false, true); deselected = Collections.singleton(selectedRow); } else { deselected = Collections.emptySet(); @@ -976,19 +1110,28 @@ public class Grid extends AbstractComponent implements SelectionNotifier, fireSelectionEvent(deselected, selection); } + if (refresh) { + refreshRow(itemId); + } + return modified; } private boolean deselect(final Object itemId) { - return deselectInternal(itemId, true); + return deselectInternal(itemId, true, true); } private boolean deselectInternal(final Object itemId, - boolean fireEventIfNeeded) { + boolean fireEventIfNeeded, boolean refresh) { final boolean modified = selection.remove(itemId); - if (fireEventIfNeeded && modified) { - fireSelectionEvent(Collections.singleton(itemId), - Collections.emptySet()); + if (modified) { + if (refresh) { + refreshRow(itemId); + } + if (fireEventIfNeeded) { + fireSelectionEvent(Collections.singleton(itemId), + Collections.emptySet()); + } } return modified; } @@ -1014,23 +1157,25 @@ public class Grid extends AbstractComponent implements SelectionNotifier, @Override public void setDeselectAllowed(boolean deselectAllowed) { - grid.getState().singleSelectDeselectAllowed = deselectAllowed; + getState().deselectAllowed = deselectAllowed; } @Override public boolean isDeselectAllowed() { - return grid.getState(false).singleSelectDeselectAllowed; + return getState().deselectAllowed; + } + + @Override + protected SingleSelectionModelState getState() { + return (SingleSelectionModelState) super.getState(); } } /** * A default implementation for a {@link SelectionModel.None} */ - public static class NoSelectionModel implements SelectionModel.None { - @Override - public void setGrid(final Grid grid) { - // NOOP, not needed for anything - } + public static class NoSelectionModel extends AbstractSelectionModel + implements SelectionModel.None { @Override public boolean isSelected(final Object itemId) { @@ -1069,6 +1214,41 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private int selectionLimit = DEFAULT_MAX_SELECTIONS; @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + registerRpc(new MultiSelectionModelServerRpc() { + + @Override + public void select(List<String> rowKeys) { + List<Object> items = new ArrayList<Object>(); + for (String rowKey : rowKeys) { + items.add(getItemId(rowKey)); + } + MultiSelectionModel.this.select(items, false); + } + + @Override + public void deselect(List<String> rowKeys) { + List<Object> items = new ArrayList<Object>(); + for (String rowKey : rowKeys) { + items.add(getItemId(rowKey)); + } + MultiSelectionModel.this.deselect(items, false); + } + + @Override + public void selectAll() { + MultiSelectionModel.this.selectAll(false); + } + + @Override + public void deselectAll() { + MultiSelectionModel.this.deselectAll(false); + } + }); + } + + @Override public boolean select(final Object... itemIds) throws IllegalArgumentException { if (itemIds != null) { @@ -1089,6 +1269,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, @Override public boolean select(final Collection<?> itemIds) throws IllegalArgumentException { + return select(itemIds, true); + } + + protected boolean select(final Collection<?> itemIds, boolean refresh) { if (itemIds == null) { throw new IllegalArgumentException("itemIds may not be null"); } @@ -1113,6 +1297,15 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } fireSelectionEvent(oldSelection, selection); } + + updateAllSelectedState(); + + if (refresh) { + for (Object itemId : itemIds) { + refreshRow(itemId); + } + } + return selectionWillChange; } @@ -1166,6 +1359,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, @Override public boolean deselect(final Collection<?> itemIds) throws IllegalArgumentException { + return deselect(itemIds, true); + } + + protected boolean deselect(final Collection<?> itemIds, boolean refresh) { if (itemIds == null) { throw new IllegalArgumentException("itemIds may not be null"); } @@ -1178,15 +1375,28 @@ public class Grid extends AbstractComponent implements SelectionNotifier, selection.removeAll(itemIds); fireSelectionEvent(oldSelection, selection); } + + updateAllSelectedState(); + + if (refresh) { + for (Object itemId : itemIds) { + refreshRow(itemId); + } + } + return hasCommonElements; } @Override public boolean selectAll() { + return selectAll(true); + } + + protected boolean selectAll(boolean refresh) { // select will fire the event - final Indexed container = grid.getContainerDataSource(); + final Indexed container = getParentGrid().getContainerDataSource(); if (container != null) { - return select(container.getItemIds()); + return select(container.getItemIds(), refresh); } else if (selection.isEmpty()) { return false; } else { @@ -1195,14 +1405,18 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * but I guess the only theoretically correct course of * action... */ - return deselectAll(); + return deselectAll(false); } } @Override public boolean deselectAll() { + return deselectAll(true); + } + + protected boolean deselectAll(boolean refresh) { // deselect will fire the event - return deselect(getSelectedRows()); + return deselect(getSelectedRows(), refresh); } /** @@ -1258,6 +1472,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, fireSelectionEvent(oldSelection, selection); } + updateAllSelectedState(); + return changed; } @@ -1271,6 +1487,17 @@ public class Grid extends AbstractComponent implements SelectionNotifier, "Vararg array of itemIds may not be null"); } } + + private void updateAllSelectedState() { + if (getState().allSelected != selection.size() >= selectionLimit) { + getState().allSelected = selection.size() >= selectionLimit; + } + } + + @Override + protected MultiSelectionModelState getState() { + return (MultiSelectionModelState) super.getState(); + } } /** @@ -1415,39 +1642,174 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** - * Callback interface for generating custom style names for data rows + * A callback interface for generating custom style names for Grid rows. * * @see Grid#setRowStyleGenerator(RowStyleGenerator) */ public interface RowStyleGenerator extends Serializable { /** - * Called by Grid to generate a style name for a row + * Called by Grid to generate a style name for a row. * - * @param rowReference - * The row to generate a style for + * @param row + * the row to generate a style for * @return the style name to add to this row, or {@code null} to not set * any style */ - public String getStyle(RowReference rowReference); + public String getStyle(RowReference row); } /** - * Callback interface for generating custom style names for cells + * A callback interface for generating custom style names for Grid cells. * * @see Grid#setCellStyleGenerator(CellStyleGenerator) */ public interface CellStyleGenerator extends Serializable { /** - * Called by Grid to generate a style name for a column + * Called by Grid to generate a style name for a column. * - * @param cellReference - * The cell to generate a style for + * @param cell + * the cell to generate a style for * @return the style name to add to this cell, or {@code null} to not * set any style */ - public String getStyle(CellReference cellReference); + public String getStyle(CellReference cell); + } + + /** + * A callback interface for generating optional descriptions (tooltips) for + * Grid rows. If a description is generated for a row, it is used for all + * the cells in the row for which a {@link CellDescriptionGenerator cell + * description} is not generated. + * + * @see Grid#setRowDescriptionGenerator(CellDescriptionGenerator) + * + * @since 7.6 + */ + public interface RowDescriptionGenerator extends Serializable { + + /** + * Called by Grid to generate a description (tooltip) for a row. The + * description may contain HTML which is rendered directly; if this is + * not desired the returned string must be escaped by the implementing + * method. + * + * @param row + * the row to generate a description for + * @return the row description or {@code null} for no description + */ + public String getDescription(RowReference row); + } + + /** + * A callback interface for generating optional descriptions (tooltips) for + * Grid cells. If a cell has both a {@link RowDescriptionGenerator row + * description} and a cell description, the latter has precedence. + * + * @see Grid#setCellDescriptionGenerator(CellDescriptionGenerator) + * + * @since 7.6 + */ + public interface CellDescriptionGenerator extends Serializable { + + /** + * Called by Grid to generate a description (tooltip) for a cell. The + * description may contain HTML which is rendered directly; if this is + * not desired the returned string must be escaped by the implementing + * method. + * + * @param cell + * the cell to generate a description for + * @return the cell description or {@code null} for no description + */ + public String getDescription(CellReference cell); + } + + /** + * Class for generating all row and cell related data for the essential + * parts of Grid. + */ + private class RowDataGenerator implements DataGenerator { + + private void put(String key, String value, JsonObject object) { + if (value != null && !value.isEmpty()) { + object.put(key, value); + } + } + + @Override + public void generateData(Object itemId, Item item, JsonObject rowData) { + RowReference row = new RowReference(Grid.this); + row.set(itemId); + + if (rowStyleGenerator != null) { + String style = rowStyleGenerator.getStyle(row); + put(GridState.JSONKEY_ROWSTYLE, style, rowData); + } + + if (rowDescriptionGenerator != null) { + String description = rowDescriptionGenerator + .getDescription(row); + put(GridState.JSONKEY_ROWDESCRIPTION, description, rowData); + + } + + JsonObject cellStyles = Json.createObject(); + JsonObject cellData = Json.createObject(); + JsonObject cellDescriptions = Json.createObject(); + + CellReference cell = new CellReference(row); + + for (Column column : getColumns()) { + cell.set(column.getPropertyId()); + + writeData(cell, cellData); + writeStyles(cell, cellStyles); + writeDescriptions(cell, cellDescriptions); + } + + if (cellDescriptionGenerator != null + && cellDescriptions.keys().length > 0) { + rowData.put(GridState.JSONKEY_CELLDESCRIPTION, cellDescriptions); + } + + if (cellStyleGenerator != null && cellStyles.keys().length > 0) { + rowData.put(GridState.JSONKEY_CELLSTYLES, cellStyles); + } + + rowData.put(GridState.JSONKEY_DATA, cellData); + } + + private void writeStyles(CellReference cell, JsonObject styles) { + if (cellStyleGenerator != null) { + String style = cellStyleGenerator.getStyle(cell); + put(columnKeys.key(cell.getPropertyId()), style, styles); + } + } + + private void writeDescriptions(CellReference cell, + JsonObject descriptions) { + if (cellDescriptionGenerator != null) { + String description = cellDescriptionGenerator + .getDescription(cell); + put(columnKeys.key(cell.getPropertyId()), description, + descriptions); + } + } + + private void writeData(CellReference cell, JsonObject data) { + Column column = getColumn(cell.getPropertyId()); + Converter<?, ?> converter = column.getConverter(); + Renderer<?> renderer = column.getRenderer(); + + Item item = cell.getItem(); + Object modelValue = item.getItemProperty(cell.getPropertyId()) + .getValue(); + + data.put(columnKeys.key(cell.getPropertyId()), AbstractRenderer + .encodeValue(modelValue, renderer, converter, getLocale())); + } } /** @@ -3281,7 +3643,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * currently extends the AbstractExtension superclass, but this fact should * be regarded as an implementation detail and subject to change in a future * major or minor Vaadin revision. - * + * * @param <T> * the type this renderer knows how to present */ @@ -3354,7 +3716,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * is desired. For instance, a {@code Renderer<Date>} could first turn a * date value into a formatted string and return * {@code encode(dateString, String.class)}. - * + * * @param value * the value to be encoded * @param type @@ -3365,11 +3727,79 @@ public class Grid extends AbstractComponent implements SelectionNotifier, return JsonCodec.encode(value, null, type, getUI().getConnectorTracker()).getEncodedValue(); } + + /** + * Converts and encodes the given data model property value using the + * given converter and renderer. This method is public only for testing + * purposes. + * + * @param renderer + * the renderer to use + * @param converter + * the converter to use + * @param modelValue + * the value to convert and encode + * @param locale + * the locale to use in conversion + * @return an encoded value ready to be sent to the client + */ + public static <T> JsonValue encodeValue(Object modelValue, + Renderer<T> renderer, Converter<?, ?> converter, Locale locale) { + Class<T> presentationType = renderer.getPresentationType(); + T presentationValue; + + if (converter == null) { + try { + presentationValue = presentationType.cast(modelValue); + } catch (ClassCastException e) { + if (presentationType == String.class) { + // If there is no converter, just fallback to using + // toString(). modelValue can't be null as + // Class.cast(null) will always succeed + presentationValue = (T) modelValue.toString(); + } else { + throw new Converter.ConversionException( + "Unable to convert value of type " + + modelValue.getClass().getName() + + " to presentation type " + + presentationType.getName() + + ". No converter is set and the types are not compatible."); + } + } + } else { + assert presentationType.isAssignableFrom(converter + .getPresentationType()); + @SuppressWarnings("unchecked") + Converter<T, Object> safeConverter = (Converter<T, Object>) converter; + presentationValue = safeConverter + .convertToPresentation(modelValue, + safeConverter.getPresentationType(), locale); + } + + JsonValue encodedValue; + try { + encodedValue = renderer.encode(presentationValue); + } catch (Exception e) { + getLogger().log(Level.SEVERE, "Unable to encode data", e); + encodedValue = renderer.encode(null); + } + + return encodedValue; + } + + private static Logger getLogger() { + return Logger.getLogger(AbstractRenderer.class.getName()); + } + } /** * An abstract base class for server-side Grid extensions. - * + * <p> + * Note: If the extension is an instance of {@link DataGenerator} it will + * automatically register itself to {@link RpcDataProviderExtension} of + * extended Grid. On remove this registration is automatically removed. + * * @since 7.5 */ public static abstract class AbstractGridExtension extends @@ -3384,7 +3814,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, /** * Constructs a new Grid extension and extends given Grid. - * + * * @param grid * a grid instance */ @@ -3393,6 +3823,26 @@ public class Grid extends AbstractComponent implements SelectionNotifier, extend(grid); } + @Override + protected void extend(AbstractClientConnector target) { + super.extend(target); + + if (this instanceof DataGenerator) { + getParentGrid().datasourceExtension + .addDataGenerator((DataGenerator) this); + } + } + + @Override + public void remove() { + if (this instanceof DataGenerator) { + getParentGrid().datasourceExtension + .removeDataGenerator((DataGenerator) this); + } + + super.remove(); + } + /** * Gets the item id for a row key. * <p> @@ -3405,7 +3855,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * @return the item id corresponding to {@code key} */ protected Object getItemId(String rowKey) { - return getParentGrid().getKeyMapper().getItemId(rowKey); + return getParentGrid().getKeyMapper().get(rowKey); } /** @@ -3434,11 +3884,27 @@ public class Grid extends AbstractComponent implements SelectionNotifier, if (getParent() instanceof Grid) { Grid grid = (Grid) getParent(); return grid; + } else if (getParent() == null) { + throw new IllegalStateException( + "Renderer is not attached to any parent"); } else { throw new IllegalStateException( - "Renderers can be used only with Grid"); + "Renderers can be used only with Grid. Extended " + + getParent().getClass().getSimpleName() + + " instead"); } } + + /** + * Resends the row data for given item id to the client. + * + * @since + * @param itemId + * row to refresh + */ + protected void refreshRow(Object itemId) { + getParentGrid().datasourceExtension.updateRowData(itemId); + } } /** @@ -3514,6 +3980,13 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } }; + private final ItemSetChangeListener editorClosingItemSetListener = new ItemSetChangeListener() { + @Override + public void containerItemSetChange(ItemSetChangeEvent event) { + cancelEditor(); + } + }; + private RpcDataProviderExtension datasourceExtension; /** @@ -3539,6 +4012,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, private CellStyleGenerator cellStyleGenerator; private RowStyleGenerator rowStyleGenerator; + private CellDescriptionGenerator cellDescriptionGenerator; + private RowDescriptionGenerator rowDescriptionGenerator; + /** * <code>true</code> if Grid is using the internal IndexedContainer created * in Grid() constructor, or <code>false</code> if the user has set their @@ -3628,117 +4104,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ private void initGrid() { setSelectionMode(getDefaultSelectionMode()); - addSelectionListener(new SelectionListener() { - @Override - public void select(SelectionEvent event) { - if (applyingSelectionFromClient) { - /* - * Avoid sending changes back to the client if they - * originated from the client. Instead, the RPC handler is - * responsible for keeping track of the resulting selection - * state and notifying the client if it doens't match the - * expectation. - */ - return; - } - - /* - * The rows are pinned here to ensure that the client gets the - * correct key from server when the selected row is first - * loaded. - * - * Once the client has gotten info that it is supposed to select - * a row, it will pin the data from the client side as well and - * it will be unpinned once it gets deselected. Nothing on the - * server side should ever unpin anything from KeyMapper. - * Pinning is mostly a client feature and is only used when - * selecting something from the server side. - */ - for (Object addedItemId : event.getAdded()) { - if (!getKeyMapper().isPinned(addedItemId)) { - getKeyMapper().pin(addedItemId); - } - } - - getState().selectedKeys = getKeyMapper().getKeys( - getSelectedRows()); - } - }); registerRpc(new GridServerRpc() { @Override - public void select(List<String> selection) { - Collection<Object> receivedSelection = getKeyMapper() - .getItemIds(selection); - - applyingSelectionFromClient = true; - try { - SelectionModel selectionModel = getSelectionModel(); - if (selectionModel instanceof SelectionModel.Single - && selection.size() <= 1) { - Object select = null; - if (selection.size() == 1) { - select = getKeyMapper().getItemId(selection.get(0)); - } - ((SelectionModel.Single) selectionModel).select(select); - } else if (selectionModel instanceof SelectionModel.Multi) { - ((SelectionModel.Multi) selectionModel) - .setSelected(receivedSelection); - } else { - throw new IllegalStateException("SelectionModel " - + selectionModel.getClass().getSimpleName() - + " does not support selecting the given " - + selection.size() + " items."); - } - } finally { - applyingSelectionFromClient = false; - } - - Collection<Object> actualSelection = getSelectedRows(); - - // Make sure all selected rows are pinned - for (Object itemId : actualSelection) { - if (!getKeyMapper().isPinned(itemId)) { - getKeyMapper().pin(itemId); - } - } - - // Don't mark as dirty since this might be the expected state - getState(false).selectedKeys = getKeyMapper().getKeys( - actualSelection); - - JsonObject diffState = getUI().getConnectorTracker() - .getDiffState(Grid.this); - - final String diffstateKey = "selectedKeys"; - - assert diffState.hasKey(diffstateKey) : "Field name has changed"; - - if (receivedSelection.equals(actualSelection)) { - /* - * We ended up with the same selection state that the client - * sent us. There's nothing to send back to the client, just - * update the diffstate so subsequent changes will be - * detected. - */ - JsonArray diffSelected = Json.createArray(); - for (String rowKey : getState(false).selectedKeys) { - diffSelected.set(diffSelected.length(), rowKey); - } - diffState.put(diffstateKey, diffSelected); - } else { - /* - * Actual selection is not what the client expects. Make - * sure the client gets a state change event by clearing the - * diffstate and marking as dirty - */ - diffState.remove(diffstateKey); - markAsDirty(); - } - } - - @Override public void sort(String[] columnIds, SortDirection[] directions, boolean userOriginated) { assert columnIds.length == directions.length; @@ -3767,16 +4136,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } @Override - public void selectAll() { - assert getSelectionModel() instanceof SelectionModel.Multi : "Not a multi selection model!"; - - ((SelectionModel.Multi) getSelectionModel()).selectAll(); - } - - @Override public void itemClick(String rowKey, String columnId, MouseEventDetails details) { - Object itemId = getKeyMapper().getItemId(rowKey); + Object itemId = getKeyMapper().get(rowKey); Item item = datasource.getItem(itemId); Object propertyId = getPropertyIdByColumnId(columnId); fireEvent(new ItemClickEvent(Grid.this, item, itemId, @@ -3858,10 +4220,21 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } @Override - public void sendDetailsComponents(int fetchId) { - getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( - detailComponentManager.getAndResetConnectorChanges(), - fetchId); + public void editorOpen(String rowKey) { + fireEvent(new EditorOpenEvent(Grid.this, getKeyMapper().get( + rowKey))); + } + + @Override + public void editorMove(String rowKey) { + fireEvent(new EditorMoveEvent(Grid.this, getKeyMapper().get( + rowKey))); + } + + @Override + public void editorClose(String rowKey) { + fireEvent(new EditorCloseEvent(Grid.this, getKeyMapper().get( + rowKey))); } }); @@ -3869,28 +4242,37 @@ public class Grid extends AbstractComponent implements SelectionNotifier, @Override public void bind(int rowIndex) { - Exception exception = null; try { Object id = getContainerDataSource().getIdByIndex(rowIndex); - if (editedItemId == null) { - editedItemId = id; - } - if (editedItemId.equals(id)) { - doEditItem(); + final boolean opening = editedItemId == null; + + final boolean moving = !opening && !editedItemId.equals(id); + + final boolean allowMove = !isEditorBuffered() + && getEditorFieldGroup().isValid(); + + if (opening || !moving || allowMove) { + doBind(id); + } else { + failBind(null); } } catch (Exception e) { - exception = e; + failBind(e); } + } - if (exception != null) { - handleError(exception); - doCancelEditor(); - getEditorRpc().confirmBind(false); - } else { - doEditItem(); - getEditorRpc().confirmBind(true); + private void doBind(Object id) { + editedItemId = id; + doEditItem(); + getEditorRpc().confirmBind(true); + } + + private void failBind(Exception e) { + if (e != null) { + handleError(e); } + getEditorRpc().confirmBind(false); } @Override @@ -3999,10 +4381,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, removeExtension(datasourceExtension); } - datasource = container; - resetEditor(); + datasource = container; + // // Adjust sort order // @@ -4031,7 +4413,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } datasourceExtension = new RpcDataProviderExtension(container); - datasourceExtension.extend(this, columnKeys); + datasourceExtension.extend(this); + datasourceExtension.addDataGenerator(new RowDataGenerator()); detailComponentManager = datasourceExtension .getDetailComponentManager(); @@ -4049,6 +4432,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, ((PropertySetChangeNotifier) datasource) .addPropertySetChangeListener(propertyListener); } + /* * activeRowHandler will be updated by the client-side request that * occurs on container change - no need to actively re-insert any @@ -4643,25 +5027,11 @@ public class Grid extends AbstractComponent implements SelectionNotifier, if (this.selectionModel != selectionModel) { // this.selectionModel is null on init if (this.selectionModel != null) { - this.selectionModel.reset(); - this.selectionModel.setGrid(null); + this.selectionModel.remove(); } this.selectionModel = selectionModel; - this.selectionModel.setGrid(this); - this.selectionModel.reset(); - - if (selectionModel.getClass().equals(SingleSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.SINGLE; - } else if (selectionModel.getClass().equals( - MultiSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.MULTI; - } else if (selectionModel.getClass().equals(NoSelectionModel.class)) { - getState().selectionMode = SharedSelectionMode.NONE; - } else { - throw new UnsupportedOperationException("Grid currently " - + "supports only its own bundled selection models"); - } + selectionModel.setGrid(this); } } @@ -4928,7 +5298,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * * @return the key mapper being used by the data source */ - DataProviderKeyMapper getKeyMapper() { + KeyMapper<Object> getKeyMapper() { return datasourceExtension.getKeyMapper(); } @@ -5489,6 +5859,73 @@ public class Grid extends AbstractComponent implements SelectionNotifier, } /** + * Sets the {@code CellDescriptionGenerator} instance for generating + * optional descriptions (tooltips) for individual Grid cells. If a + * {@link RowDescriptionGenerator} is also set, the row description it + * generates is displayed for cells for which {@code generator} returns + * null. + * + * @param generator + * the description generator to use or {@code null} to remove a + * previously set generator if any + * + * @see #setRowDescriptionGenerator(RowDescriptionGenerator) + * + * @since 7.6 + */ + public void setCellDescriptionGenerator(CellDescriptionGenerator generator) { + cellDescriptionGenerator = generator; + getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null); + datasourceExtension.refreshCache(); + } + + /** + * Returns the {@code CellDescriptionGenerator} instance used to generate + * descriptions (tooltips) for Grid cells. + * + * @return the description generator or {@code null} if no generator is set + * + * @since 7.6 + */ + public CellDescriptionGenerator getCellDescriptionGenerator() { + return cellDescriptionGenerator; + } + + /** + * Sets the {@code RowDescriptionGenerator} instance for generating optional + * descriptions (tooltips) for Grid rows. If a + * {@link CellDescriptionGenerator} is also set, the row description + * generated by {@code generator} is used for cells for which the cell + * description generator returns null. + * + * + * @param generator + * the description generator to use or {@code null} to remove a + * previously set generator if any + * + * @see #setCellDescriptionGenerator(CellDescriptionGenerator) + * + * @since 7.6 + */ + public void setRowDescriptionGenerator(RowDescriptionGenerator generator) { + rowDescriptionGenerator = generator; + getState().hasDescriptions = (generator != null || cellDescriptionGenerator != null); + datasourceExtension.refreshCache(); + } + + /** + * Returns the {@code RowDescriptionGenerator} instance used to generate + * descriptions (tooltips) for Grid rows + * + * @return the description generator or {@code} null if no generator is set + * + * @since 7.6 + */ + public RowDescriptionGenerator getRowDescriptionGenerator() { + return rowDescriptionGenerator; + } + + /** * Sets the style generator that is used for generating styles for cells * * @param cellStyleGenerator @@ -5497,8 +5934,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ public void setCellStyleGenerator(CellStyleGenerator cellStyleGenerator) { this.cellStyleGenerator = cellStyleGenerator; - getState().hasCellStyleGenerator = (cellStyleGenerator != null); - datasourceExtension.refreshCache(); } @@ -5521,8 +5956,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier, */ public void setRowStyleGenerator(RowStyleGenerator rowStyleGenerator) { this.rowStyleGenerator = rowStyleGenerator; - getState().hasRowStyleGenerator = (rowStyleGenerator != null); - datasourceExtension.refreshCache(); } @@ -5732,10 +6165,14 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * Opens the editor interface for the provided item. Scrolls the Grid to * bring the item to view if it is not already visible. * + * Note that any cell content rendered by a WidgetRenderer will not be + * visible in the editor row. + * * @param itemId * the id of the item to edit * @throws IllegalStateException - * if the editor is not enabled or already editing an item + * if the editor is not enabled or already editing an item in + * buffered mode * @throws IllegalArgumentException * if the {@code itemId} is not in the backing container * @see #setEditorEnabled(boolean) @@ -5744,8 +6181,8 @@ public class Grid extends AbstractComponent implements SelectionNotifier, IllegalArgumentException { if (!isEditorEnabled()) { throw new IllegalStateException("Item editor is not enabled"); - } else if (editedItemId != null) { - throw new IllegalStateException("Editing item + " + itemId + } else if (isEditorBuffered() && editedItemId != null) { + throw new IllegalStateException("Editing item " + itemId + " failed. Item editor is already editing item " + editedItemId); } else if (!getContainerDataSource().containsId(itemId)) { @@ -5773,6 +6210,10 @@ public class Grid extends AbstractComponent implements SelectionNotifier, f.markAsDirtyRecursive(); } + if (datasource instanceof ItemSetChangeNotifier) { + ((ItemSetChangeNotifier) datasource) + .addItemSetChangeListener(editorClosingItemSetListener); + } } private void setEditorField(Object propertyId, Field<?> field) { @@ -5822,6 +6263,11 @@ public class Grid extends AbstractComponent implements SelectionNotifier, editorFieldGroup.discard(); editorFieldGroup.setItemDataSource(null); + if (datasource instanceof ItemSetChangeNotifier) { + ((ItemSetChangeNotifier) datasource) + .removeItemSetChangeListener(editorClosingItemSetListener); + } + // Mark Grid as dirty so the client side gets to know that the editors // are no longer attached markAsDirty(); @@ -5971,6 +6417,70 @@ public class Grid extends AbstractComponent implements SelectionNotifier, return getState(false).editorCancelCaption; } + /** + * Add an editor event listener + * + * @param listener + * the event listener object to add + */ + public void addEditorListener(EditorListener listener) { + addListener(GridConstants.EDITOR_OPEN_EVENT_ID, EditorOpenEvent.class, + listener, EditorListener.EDITOR_OPEN_METHOD); + addListener(GridConstants.EDITOR_MOVE_EVENT_ID, EditorMoveEvent.class, + listener, EditorListener.EDITOR_MOVE_METHOD); + addListener(GridConstants.EDITOR_CLOSE_EVENT_ID, + EditorCloseEvent.class, listener, + EditorListener.EDITOR_CLOSE_METHOD); + } + + /** + * Remove an editor event listener + * + * @param listener + * the event listener object to remove + */ + public void removeEditorListener(EditorListener listener) { + removeListener(GridConstants.EDITOR_OPEN_EVENT_ID, + EditorOpenEvent.class, listener); + removeListener(GridConstants.EDITOR_MOVE_EVENT_ID, + EditorMoveEvent.class, listener); + removeListener(GridConstants.EDITOR_CLOSE_EVENT_ID, + EditorCloseEvent.class, listener); + } + + /** + * Sets the buffered editor mode. The default mode is buffered ( + * <code>true</code>). + * + * @since 7.6 + * @param editorBuffered + * <code>true</code> to enable buffered editor, + * <code>false</code> to disable it + * @throws IllegalStateException + * If editor is active while attempting to change the buffered + * mode. + */ + public void setEditorBuffered(boolean editorBuffered) + throws IllegalStateException { + if (isEditorActive()) { + throw new IllegalStateException( + "Can't change editor unbuffered mode while editor is active."); + } + getState().editorBuffered = editorBuffered; + editorFieldGroup.setBuffered(editorBuffered); + } + + /** + * Gets the buffered editor mode. + * + * @since 7.6 + * @return <code>true</code> if buffered editor is enabled, + * <code>false</code> otherwise + */ + public boolean isEditorBuffered() { + return getState(false).editorBuffered; + } + @Override public void addItemClickListener(ItemClickListener listener) { addListener(GridConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class, @@ -6063,8 +6573,6 @@ public class Grid extends AbstractComponent implements SelectionNotifier, this.detailsGenerator = detailsGenerator; datasourceExtension.refreshDetails(); - getRpcProxy(GridClientRpc.class).setDetailsConnectorChanges( - detailComponentManager.getAndResetConnectorChanges(), -1); } /** @@ -6088,6 +6596,9 @@ public class Grid extends AbstractComponent implements SelectionNotifier, * to hide them */ public void setDetailsVisible(Object itemId, boolean visible) { + if (DetailsGenerator.NULL.equals(detailsGenerator)) { + return; + } datasourceExtension.setDetailsVisible(itemId, visible); } @@ -6265,6 +6776,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, result.add("footer-visible"); result.add("editor-error-handler"); result.add("height-mode"); + return result; } } diff --git a/server/src/com/vaadin/ui/HorizontalLayout.java b/server/src/com/vaadin/ui/HorizontalLayout.java index 54569570b9..616fa09225 100644 --- a/server/src/com/vaadin/ui/HorizontalLayout.java +++ b/server/src/com/vaadin/ui/HorizontalLayout.java @@ -15,6 +15,8 @@ */ package com.vaadin.ui; +import com.vaadin.shared.ui.orderedlayout.HorizontalLayoutState; + /** * Horizontal layout * @@ -48,4 +50,9 @@ public class HorizontalLayout extends AbstractOrderedLayout { addComponents(children); } + @Override + protected HorizontalLayoutState getState() { + return (HorizontalLayoutState) super.getState(); + } + } diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java index 42c4beab6c..10d1c45ab6 100644 --- a/server/src/com/vaadin/ui/Table.java +++ b/server/src/com/vaadin/ui/Table.java @@ -69,6 +69,7 @@ import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.declarative.DesignAttributeHandler; import com.vaadin.ui.declarative.DesignContext; import com.vaadin.ui.declarative.DesignException; +import com.vaadin.util.ReflectTools; /** * <p> @@ -1310,6 +1311,8 @@ public class Table extends AbstractSelect implements Action.Container, * the desired collapsedness. * @throws IllegalStateException * if column collapsing is not allowed + * @throws IllegalArgumentException + * if the property id does not exist */ public void setColumnCollapsed(Object propertyId, boolean collapsed) throws IllegalStateException { @@ -1319,11 +1322,20 @@ public class Table extends AbstractSelect implements Action.Container, if (collapsed && noncollapsibleColumns.contains(propertyId)) { throw new IllegalStateException("The column is noncollapsible!"); } + if (!getContainerPropertyIds().contains(propertyId) + && !columnGenerators.containsKey(propertyId)) { + throw new IllegalArgumentException("Property '" + propertyId + + "' was not found in the container"); + } if (collapsed) { - collapsedColumns.add(propertyId); + if (collapsedColumns.add(propertyId)) { + fireColumnCollapseEvent(propertyId); + } } else { - collapsedColumns.remove(propertyId); + if (collapsedColumns.remove(propertyId)) { + fireColumnCollapseEvent(propertyId); + } } // Assures the visual refresh @@ -3182,6 +3194,10 @@ public class Table extends AbstractSelect implements Action.Container, } } + private void fireColumnCollapseEvent(Object propertyId) { + fireEvent(new ColumnCollapseEvent(this, propertyId)); + } + private void fireColumnResizeEvent(Object propertyId, int previousWidth, int currentWidth) { /* @@ -5742,6 +5758,53 @@ public class Table extends AbstractSelect implements Action.Container, } /** + * This event is fired when the collapse state of a column changes + */ + public static class ColumnCollapseEvent extends Component.Event { + + public static final Method METHOD = ReflectTools.findMethod( + ColumnCollapseListener.class, "columnCollapseStateChange", + ColumnCollapseEvent.class); + private Object propertyId; + + /** + * Constructor + * + * @param source + * The source of the event + * @param propertyId + * The id of the column + */ + public ColumnCollapseEvent(Component source, Object propertyId) { + super(source); + this.propertyId = propertyId; + } + + /** + * Gets the id of the column whose collapse state changed + * + * @return the property id of the column + */ + public Object getPropertyId() { + return propertyId; + } + } + + /** + * Interface for listening to column collapse events. + */ + public interface ColumnCollapseListener extends Serializable { + + /** + * This method is triggered when the collapse state for a column has + * changed + * + * @param event + */ + public void columnCollapseStateChange(ColumnCollapseEvent event); + } + + /** * Adds a column reorder listener to the Table. A column reorder listener is * called when a user reorders columns. * @@ -5783,6 +5846,29 @@ public class Table extends AbstractSelect implements Action.Container, } /** + * Adds a column collapse listener to the Table. A column collapse listener + * is called when the collapsed state of a column changes. + * + * @param listener + * The listener to attach + */ + public void addColumnCollapseListener(ColumnCollapseListener listener) { + addListener(TableConstants.COLUMN_COLLAPSE_EVENT_ID, + ColumnCollapseEvent.class, listener, ColumnCollapseEvent.METHOD); + } + + /** + * Removes a column reorder listener from the Table. + * + * @param listener + * The listener to remove + */ + public void removeColumnCollapseListener(ColumnCollapseListener listener) { + removeListener(TableConstants.COLUMN_COLLAPSE_EVENT_ID, + ColumnCollapseEvent.class, listener); + } + + /** * Set the item description generator which generates tooltips for cells and * rows in the Table * diff --git a/server/src/com/vaadin/ui/VerticalLayout.java b/server/src/com/vaadin/ui/VerticalLayout.java index 12819e50bc..7002fbc7e6 100644 --- a/server/src/com/vaadin/ui/VerticalLayout.java +++ b/server/src/com/vaadin/ui/VerticalLayout.java @@ -15,6 +15,8 @@ */ package com.vaadin.ui; +import com.vaadin.shared.ui.orderedlayout.VerticalLayoutState; + /** * Vertical layout * @@ -48,4 +50,9 @@ public class VerticalLayout extends AbstractOrderedLayout { this(); addComponents(children); } + + @Override + protected VerticalLayoutState getState() { + return (VerticalLayoutState) super.getState(); + } } diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 61ba5826b8..fd5565f900 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -21,6 +21,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -74,7 +75,7 @@ import com.vaadin.util.ReflectTools; * @author Vaadin Ltd. * @since 3.0 */ -@SuppressWarnings("serial") +@SuppressWarnings({ "serial", "deprecation" }) public class Window extends Panel implements FocusNotifier, BlurNotifier, LegacyComponent { @@ -102,6 +103,11 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, }; /** + * Holds registered CloseShortcut instances for query and later removal + */ + private List<CloseShortcut> closeShortcuts = new ArrayList<CloseShortcut>(4); + + /** * Creates a new, empty window */ public Window() { @@ -130,6 +136,7 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, super(caption, content); registerRpc(rpc); setSizeUndefined(); + setCloseShortcut(KeyCode.ESCAPE); } /* ********************************************************************* */ @@ -828,14 +835,22 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, } } - /* - * Actions - */ - protected CloseShortcut closeShortcut; - /** - * Makes is possible to close the window by pressing the given - * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/> + * This is the old way of adding a keyboard shortcut to close a + * {@link Window} - to preserve compatibility with existing code under the + * new functionality, this method now first removes all registered close + * shortcuts, then adds the default ESCAPE shortcut key, and then attempts + * to add the shortcut provided as parameters to this method. This method, + * and its companion {@link #removeCloseShortcut()}, are now considered + * deprecated, as their main function is to preserve exact backwards + * compatibility with old code. For all new code, use the new keyboard + * shortcuts API: {@link #addCloseShortcut(int,int...)}, + * {@link #removeCloseShortcut(int,int...)}, + * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)} + * and {@link #getCloseShortcuts()}. + * <p> + * Original description: Makes it possible to close the window by pressing + * the given {@link KeyCode} and (optional) {@link ModifierKey}s.<br/> * Note that this shortcut only reacts while the window has focus, closing * itself - if you want to close a window from a UI, use * {@link UI#addAction(com.vaadin.event.Action)} of the UI instead. @@ -843,29 +858,137 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, * @param keyCode * the keycode for invoking the shortcut * @param modifiers - * the (optional) modifiers for invoking the shortcut, null for - * none + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + * + * @deprecated Use {@link #addCloseShortcut(int, int...)} instead. */ + @Deprecated public void setCloseShortcut(int keyCode, int... modifiers) { - if (closeShortcut != null) { - removeAction(closeShortcut); - } - closeShortcut = new CloseShortcut(this, keyCode, modifiers); - addAction(closeShortcut); + removeCloseShortcut(); + addCloseShortcut(keyCode, modifiers); } /** - * Removes the keyboard shortcut previously set with - * {@link #setCloseShortcut(int, int...)}. + * Removes all keyboard shortcuts previously set with + * {@link #setCloseShortcut(int, int...)} and + * {@link #addCloseShortcut(int, int...)}, then adds the default + * {@link KeyCode#ESCAPE} shortcut. + * <p> + * This is the old way of removing the (single) keyboard close shortcut, and + * is retained only for exact backwards compatibility. For all new code, use + * the new keyboard shortcuts API: {@link #addCloseShortcut(int,int...)}, + * {@link #removeCloseShortcut(int,int...)}, + * {@link #removeAllCloseShortcuts()}, {@link #hasCloseShortcut(int,int...)} + * and {@link #getCloseShortcuts()}. + * + * @deprecated Use {@link #removeCloseShortcut(int, int...)} instead. */ + @Deprecated public void removeCloseShortcut() { - if (closeShortcut != null) { - removeAction(closeShortcut); - closeShortcut = null; + for (int i = 0; i < closeShortcuts.size(); ++i) { + CloseShortcut sc = closeShortcuts.get(i); + removeAction(sc); + } + closeShortcuts.clear(); + addCloseShortcut(KeyCode.ESCAPE); + } + + /** + * Adds a close shortcut - pressing this key while holding down all (if any) + * modifiers specified while this Window is in focus will close the Window. + * + * @since + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + */ + public void addCloseShortcut(int keyCode, int... modifiers) { + + // Ignore attempts to re-add existing shortcuts + if (hasCloseShortcut(keyCode, modifiers)) { + return; + } + + // Actually add the shortcut + CloseShortcut shortcut = new CloseShortcut(this, keyCode, modifiers); + addAction(shortcut); + closeShortcuts.add(shortcut); + } + + /** + * Removes a close shortcut previously added with + * {@link #addCloseShortcut(int, int...)}. + * + * @since + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + */ + public void removeCloseShortcut(int keyCode, int... modifiers) { + for (CloseShortcut shortcut : closeShortcuts) { + if (shortcut.equals(keyCode, modifiers)) { + removeAction(shortcut); + closeShortcuts.remove(shortcut); + return; + } } } /** + * Removes all close shortcuts. This includes the default ESCAPE shortcut. + * It is up to the user to add back any and all keyboard close shortcuts + * they may require. For more fine-grained control over shortcuts, use + * {@link #removeCloseShortcut(int, int...)}. + * + * @since + */ + public void removeAllCloseShortcuts() { + for (CloseShortcut shortcut : closeShortcuts) { + removeAction(shortcut); + } + closeShortcuts.clear(); + } + + /** + * Checks if a close window shortcut key has already been registered. + * + * @since + * @param keyCode + * the keycode for invoking the shortcut + * @param modifiers + * the (optional) modifiers for invoking the shortcut. Can be set + * to null to be explicit about not having modifiers. + * @return true, if an exactly matching shortcut has been registered. + */ + public boolean hasCloseShortcut(int keyCode, int... modifiers) { + for (CloseShortcut shortcut : closeShortcuts) { + if (shortcut.equals(keyCode, modifiers)) { + return true; + } + } + return false; + } + + /** + * Returns an unmodifiable collection of {@link CloseShortcut} objects + * currently registered with this {@link Window}. This method is provided + * mainly so that users can implement their own serialization routines. To + * check if a certain combination of keys has been registered as a close + * shortcut, use the {@link #hasCloseShortcut(int, int...)} method instead. + * + * @since + * @return an unmodifiable Collection of CloseShortcut objects. + */ + public Collection<CloseShortcut> getCloseShortcuts() { + return Collections.unmodifiableCollection(closeShortcuts); + } + + /** * A {@link ShortcutListener} specifically made to define a keyboard * shortcut that closes the window. * @@ -930,6 +1053,25 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, public void handleAction(Object sender, Object target) { window.close(); } + + public boolean equals(int keyCode, int... modifiers) { + if (keyCode != getKeyCode()) { + return false; + } + + if (getModifiers() != null) { + int[] mods = null; + if (modifiers != null) { + // Modifiers provided by the parent ShortcutAction class + // are guaranteed to be sorted. We still need to sort + // the modifiers passed in as argument. + mods = Arrays.copyOf(modifiers, modifiers.length); + Arrays.sort(mods); + } + return Arrays.equals(mods, getModifiers()); + } + return true; + } } /* @@ -1244,11 +1386,26 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, setPositionX(Integer.parseInt(position[0])); setPositionY(Integer.parseInt(position[1])); } + + // Parse shortcuts if defined, otherwise rely on default behavior if (design.hasAttr("close-shortcut")) { - ShortcutAction shortcut = DesignAttributeHandler - .readAttribute("close-shortcut", design.attributes(), - ShortcutAction.class); - setCloseShortcut(shortcut.getKeyCode(), shortcut.getModifiers()); + + // Parse shortcuts + String[] shortcutStrings = DesignAttributeHandler.readAttribute( + "close-shortcut", design.attributes(), String.class).split( + "\\s+"); + + removeAllCloseShortcuts(); + + for (String part : shortcutStrings) { + if (!part.isEmpty()) { + ShortcutAction shortcut = DesignAttributeHandler + .getFormatter().parse(part.trim(), + ShortcutAction.class); + addCloseShortcut(shortcut.getKeyCode(), + shortcut.getModifiers()); + } + } } } @@ -1302,19 +1459,24 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, DesignAttributeHandler.writeAttribute("position", design.attributes(), getPosition(), def.getPosition(), String.class); - CloseShortcut shortcut = getCloseShortcut(); - if (shortcut != null) { - // TODO What if several close shortcuts?? - - CloseShortcut defShortcut = def.getCloseShortcut(); - if (defShortcut == null - || shortcut.getKeyCode() != defShortcut.getKeyCode() - || !Arrays.equals(shortcut.getModifiers(), - defShortcut.getModifiers())) { - DesignAttributeHandler.writeAttribute("close-shortcut", - design.attributes(), shortcut, null, - CloseShortcut.class); + // Process keyboard shortcuts + if (closeShortcuts.size() == 1 && hasCloseShortcut(KeyCode.ESCAPE)) { + // By default, we won't write anything if we're relying on default + // shortcut behavior + } else { + // Dump all close shortcuts to a string... + String attrString = ""; + + // TODO: add canonical support for array data in XML attributes + for (CloseShortcut shortcut : closeShortcuts) { + String shortcutString = DesignAttributeHandler.getFormatter() + .format(shortcut, CloseShortcut.class); + attrString += shortcutString + " "; } + + // Write everything except the last "," to the design + DesignAttributeHandler.writeAttribute("close-shortcut", + design.attributes(), attrString.trim(), null, String.class); } for (Component c : getAssistiveDescription()) { @@ -1328,10 +1490,6 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, return getPositionX() + "," + getPositionY(); } - private CloseShortcut getCloseShortcut() { - return closeShortcut; - } - @Override protected Collection<String> getCustomAttributes() { Collection<String> result = super.getCustomAttributes(); diff --git a/server/src/com/vaadin/ui/renderers/ImageRenderer.java b/server/src/com/vaadin/ui/renderers/ImageRenderer.java index 2fb872583e..ad7d5cae2b 100644 --- a/server/src/com/vaadin/ui/renderers/ImageRenderer.java +++ b/server/src/com/vaadin/ui/renderers/ImageRenderer.java @@ -58,7 +58,7 @@ public class ImageRenderer extends ClickableRenderer<Resource> { if (!(resource == null || resource instanceof ExternalResource || resource instanceof ThemeResource)) { throw new IllegalArgumentException( "ImageRenderer only supports ExternalResource and ThemeResource (" - + resource.getClass().getSimpleName() + "given )"); + + resource.getClass().getSimpleName() + " given)"); } return encode(ResourceReference.create(resource, this, null), diff --git a/server/src/com/vaadin/ui/themes/ValoTheme.java b/server/src/com/vaadin/ui/themes/ValoTheme.java index 1285bf7f67..3a9986c632 100644 --- a/server/src/com/vaadin/ui/themes/ValoTheme.java +++ b/server/src/com/vaadin/ui/themes/ValoTheme.java @@ -709,7 +709,7 @@ public class ValoTheme { * area is scrolled. Suitable with the {@link #PANEL_BORDERLESS} style. Can * be combined with any other Panel style. */ - public static final String PANEL_SCROLL_INDICATOR = "scroll-indicator"; + public static final String PANEL_SCROLL_INDICATOR = "scroll-divider"; /** * Inset panel style. Can be combined with any other Panel style. diff --git a/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java b/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java index 54cbc5305d..55dccc6455 100644 --- a/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java +++ b/server/tests/src/com/vaadin/data/util/AbstractContainerTestBase.java @@ -11,6 +11,7 @@ import com.vaadin.data.Container; import com.vaadin.data.Container.Filterable; import com.vaadin.data.Container.ItemSetChangeEvent; import com.vaadin.data.Container.ItemSetChangeListener; +import com.vaadin.data.Container.Ordered; import com.vaadin.data.Container.Sortable; import com.vaadin.data.Item; import com.vaadin.data.util.filter.SimpleStringFilter; @@ -161,54 +162,54 @@ public abstract class AbstractContainerTestBase extends TestCase { validateContainer(container, sampleData[0], sampleData[sampleData.length - 1], sampleData[10], "abc", true, sampleData.length); + + validateRemovingItems(container); + } + + protected void validateRemovingItems(Container container) { + int sizeBeforeRemoving = container.size(); + + List<Object> itemIdList = new ArrayList<Object>(container.getItemIds()); + // There should be at least four items in the list + Object first = itemIdList.get(0); + Object middle = itemIdList.get(2); + Object last = itemIdList.get(itemIdList.size() - 1); + + container.removeItem(first); + container.removeItem(middle); // Middle now that first has been removed + container.removeItem(last); + + assertEquals(sizeBeforeRemoving - 3, container.size()); + + container.removeAllItems(); + + assertEquals(0, container.size()); } protected void testContainerOrdered(Container.Ordered container) { + // addItem with empty container Object id = container.addItem(); - assertNotNull(id); + assertOrderedContents(container, id); Item item = container.getItem(id); assertNotNull(item); - assertEquals(id, container.firstItemId()); - assertEquals(id, container.lastItemId()); - - // isFirstId - assertTrue(container.isFirstId(id)); - assertTrue(container.isFirstId(container.firstItemId())); - // isLastId - assertTrue(container.isLastId(id)); - assertTrue(container.isLastId(container.lastItemId())); + // addItemAfter with empty container + container.removeAllItems(); + assertOrderedContents(container); + id = container.addItemAfter(null); + assertOrderedContents(container, id); + item = container.getItem(id); + assertNotNull(item); // Add a new item before the first // addItemAfter Object newFirstId = container.addItemAfter(null); - assertNotNull(newFirstId); - assertNotNull(container.getItem(newFirstId)); - - // isFirstId - assertTrue(container.isFirstId(newFirstId)); - assertTrue(container.isFirstId(container.firstItemId())); - // isLastId - assertTrue(container.isLastId(id)); - assertTrue(container.isLastId(container.lastItemId())); - - // nextItemId - assertEquals(id, container.nextItemId(newFirstId)); - assertNull(container.nextItemId(id)); - assertNull(container.nextItemId("not-in-container")); - - // prevItemId - assertEquals(newFirstId, container.prevItemId(id)); - assertNull(container.prevItemId(newFirstId)); - assertNull(container.prevItemId("not-in-container")); + assertOrderedContents(container, newFirstId, id); // addItemAfter(Object) Object newSecondItemId = container.addItemAfter(newFirstId); // order is now: newFirstId, newSecondItemId, id - assertNotNull(newSecondItemId); - assertNotNull(container.getItem(newSecondItemId)); - assertEquals(id, container.nextItemId(newSecondItemId)); - assertEquals(newFirstId, container.prevItemId(newSecondItemId)); + assertOrderedContents(container, newFirstId, newSecondItemId, id); // addItemAfter(Object,Object) String fourthId = "id of the fourth item"; @@ -216,8 +217,8 @@ public abstract class AbstractContainerTestBase extends TestCase { // order is now: newFirstId, fourthId, newSecondItemId, id assertNotNull(fourth); assertEquals(fourth, container.getItem(fourthId)); - assertEquals(newSecondItemId, container.nextItemId(fourthId)); - assertEquals(newFirstId, container.prevItemId(fourthId)); + assertOrderedContents(container, newFirstId, fourthId, newSecondItemId, + id); // addItemAfter(Object,Object) Object fifthId = new Object(); @@ -225,8 +226,86 @@ public abstract class AbstractContainerTestBase extends TestCase { // order is now: fifthId, newFirstId, fourthId, newSecondItemId, id assertNotNull(fifth); assertEquals(fifth, container.getItem(fifthId)); - assertEquals(newFirstId, container.nextItemId(fifthId)); - assertNull(container.prevItemId(fifthId)); + assertOrderedContents(container, fifthId, newFirstId, fourthId, + newSecondItemId, id); + + // addItemAfter(Object,Object) + Object sixthId = new Object(); + Item sixth = container.addItemAfter(id, sixthId); + // order is now: fifthId, newFirstId, fourthId, newSecondItemId, id, + // sixthId + assertNotNull(sixth); + assertEquals(sixth, container.getItem(sixthId)); + assertOrderedContents(container, fifthId, newFirstId, fourthId, + newSecondItemId, id, sixthId); + + // Test order after removing first item 'fifthId' + container.removeItem(fifthId); + // order is now: newFirstId, fourthId, newSecondItemId, id, sixthId + assertOrderedContents(container, newFirstId, fourthId, newSecondItemId, + id, sixthId); + + // Test order after removing last item 'sixthId' + container.removeItem(sixthId); + // order is now: newFirstId, fourthId, newSecondItemId, id + assertOrderedContents(container, newFirstId, fourthId, newSecondItemId, + id); + + // Test order after removing item from the middle 'fourthId' + container.removeItem(fourthId); + // order is now: newFirstId, newSecondItemId, id + assertOrderedContents(container, newFirstId, newSecondItemId, id); + + // Delete remaining items + container.removeItem(newFirstId); + container.removeItem(newSecondItemId); + container.removeItem(id); + assertOrderedContents(container); + + Object finalItem = container.addItem(); + assertOrderedContents(container, finalItem); + } + + private void assertOrderedContents(Ordered container, Object... ids) { + assertEquals(ids.length, container.size()); + for (int i = 0; i < ids.length - 1; i++) { + assertNotNull("The item id should not be null", ids[i]); + } + if (ids.length == 0) { + assertNull("The first id is wrong", container.firstItemId()); + assertNull("The last id is wrong", container.lastItemId()); + return; + } + + assertEquals("The first id is wrong", ids[0], container.firstItemId()); + assertEquals("The last id is wrong", ids[ids.length - 1], + container.lastItemId()); + + // isFirstId & isLastId + assertTrue(container.isFirstId(container.firstItemId())); + assertTrue(container.isLastId(container.lastItemId())); + + // nextId + Object ref = container.firstItemId(); + for (int i = 1; i < ids.length; i++) { + Object next = container.nextItemId(ref); + assertEquals("The id after " + ref + " is wrong", ids[i], next); + ref = next; + } + assertNull("The last id should not have a next id", + container.nextItemId(ids[ids.length - 1])); + assertNull(container.nextItemId("not-in-container")); + + // prevId + ref = container.lastItemId(); + for (int i = ids.length - 2; i >= 0; i--) { + Object prev = container.prevItemId(ref); + assertEquals("The id before " + ref + " is wrong", ids[i], prev); + ref = prev; + } + assertNull("The first id should not have a prev id", + container.prevItemId(ids[0])); + assertNull(container.prevItemId("not-in-container")); } diff --git a/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java b/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java index 3bd00cce3c..9cede77162 100644 --- a/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java +++ b/server/tests/src/com/vaadin/data/util/AbstractHierarchicalContainerTestBase.java @@ -253,4 +253,33 @@ public abstract class AbstractHierarchicalContainerTestBase extends } } + protected void testRemoveHierarchicalWrapperSubtree( + Container.Hierarchical container) { + initializeContainer(container); + + // remove root item + removeItemRecursively(container, "org"); + + int packages = 21 + 3 - 3; + int expectedSize = sampleData.length + packages - 1; + + validateContainer(container, "com", "com.vaadin.util.SerializerHelper", + "com.vaadin.server.ApplicationResource", "blah", true, + expectedSize); + + // rootItemIds + Collection<?> rootIds = container.rootItemIds(); + assertEquals(1, rootIds.size()); + } + + private void removeItemRecursively(Container.Hierarchical container, + Object itemId) { + if (container instanceof ContainerHierarchicalWrapper) { + ((ContainerHierarchicalWrapper) container) + .removeItemRecursively("org"); + } else { + HierarchicalContainer.removeItemRecursively(container, itemId); + } + } + } diff --git a/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java b/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java index 2fd21ef118..8e554d5030 100644 --- a/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java +++ b/server/tests/src/com/vaadin/data/util/ContainerHierarchicalWrapperTest.java @@ -1,6 +1,5 @@ package com.vaadin.data.util; -import java.util.Collection; public class ContainerHierarchicalWrapperTest extends AbstractHierarchicalContainerTestBase { @@ -20,23 +19,4 @@ public class ContainerHierarchicalWrapperTest extends new IndexedContainer())); } - protected void testRemoveHierarchicalWrapperSubtree( - ContainerHierarchicalWrapper container) { - initializeContainer(container); - - // remove root item - container.removeItemRecursively("org"); - - int packages = 21 + 3 - 3; - int expectedSize = sampleData.length + packages - 1; - - validateContainer(container, "com", "com.vaadin.util.SerializerHelper", - "com.vaadin.server.ApplicationResource", "blah", true, - expectedSize); - - // rootItemIds - Collection<?> rootIds = container.rootItemIds(); - assertEquals(1, rootIds.size()); - } - } diff --git a/server/tests/src/com/vaadin/data/util/ContainerOrderedWrapperTest.java b/server/tests/src/com/vaadin/data/util/ContainerOrderedWrapperTest.java new file mode 100644 index 0000000000..422e9756f8 --- /dev/null +++ b/server/tests/src/com/vaadin/data/util/ContainerOrderedWrapperTest.java @@ -0,0 +1,102 @@ +package com.vaadin.data.util; + +import java.util.Collection; + +import com.vaadin.data.Container; +import com.vaadin.data.Item; +import com.vaadin.data.Property; + +public class ContainerOrderedWrapperTest extends AbstractContainerTestBase { + + // This class is needed to get an implementation of container + // which is not an implementation of Ordered interface. + private class NotOrderedContainer implements Container { + + private IndexedContainer container; + + public NotOrderedContainer() { + container = new IndexedContainer(); + } + + @Override + public Item getItem(Object itemId) { + return container.getItem(itemId); + } + + @Override + public Collection<?> getContainerPropertyIds() { + return container.getContainerPropertyIds(); + } + + @Override + public Collection<?> getItemIds() { + return container.getItemIds(); + } + + @Override + public Property getContainerProperty(Object itemId, Object propertyId) { + return container.getContainerProperty(itemId, propertyId); + } + + @Override + public Class<?> getType(Object propertyId) { + return container.getType(propertyId); + } + + @Override + public int size() { + return container.size(); + } + + @Override + public boolean containsId(Object itemId) { + return container.containsId(itemId); + } + + @Override + public Item addItem(Object itemId) throws UnsupportedOperationException { + return container.addItem(itemId); + } + + @Override + public Object addItem() throws UnsupportedOperationException { + return container.addItem(); + } + + @Override + public boolean removeItem(Object itemId) + throws UnsupportedOperationException { + return container.removeItem(itemId); + } + + @Override + public boolean addContainerProperty(Object propertyId, Class<?> type, + Object defaultValue) throws UnsupportedOperationException { + return container.addContainerProperty(propertyId, type, + defaultValue); + } + + @Override + public boolean removeContainerProperty(Object propertyId) + throws UnsupportedOperationException { + return container.removeContainerProperty(propertyId); + } + + @Override + public boolean removeAllItems() throws UnsupportedOperationException { + return container.removeAllItems(); + } + + } + + public void testBasicOperations() { + testBasicContainerOperations(new ContainerOrderedWrapper( + new NotOrderedContainer())); + } + + public void testOrdered() { + testContainerOrdered(new ContainerOrderedWrapper( + new NotOrderedContainer())); + } + +} diff --git a/server/tests/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapperTest.java b/server/tests/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapperTest.java new file mode 100644 index 0000000000..7ecf59c309 --- /dev/null +++ b/server/tests/src/com/vaadin/data/util/HierarchicalContainerOrderedWrapperTest.java @@ -0,0 +1,27 @@ +package com.vaadin.data.util; + +public class HierarchicalContainerOrderedWrapperTest extends + AbstractHierarchicalContainerTestBase { + + private HierarchicalContainerOrderedWrapper createContainer() { + return new HierarchicalContainerOrderedWrapper( + new ContainerHierarchicalWrapper(new IndexedContainer())); + } + + public void testBasicOperations() { + testBasicContainerOperations(createContainer()); + } + + public void testHierarchicalContainer() { + testHierarchicalContainer(createContainer()); + } + + public void testContainerOrdered() { + testContainerOrdered(createContainer()); + } + + public void testRemoveSubtree() { + testRemoveHierarchicalWrapperSubtree(createContainer()); + } + +} diff --git a/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java b/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java index 2cb4c3bf4d..286163541e 100644 --- a/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java +++ b/server/tests/src/com/vaadin/server/communication/FileUploadHandlerTest.java @@ -15,50 +15,136 @@ */ package com.vaadin.server.communication; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import com.vaadin.server.ClientConnector; +import com.vaadin.server.ServletPortletHelper; +import com.vaadin.server.StreamVariable; import com.vaadin.server.VaadinRequest; +import com.vaadin.server.VaadinResponse; +import com.vaadin.server.VaadinSession; +import com.vaadin.ui.ConnectorTracker; +import com.vaadin.ui.UI; -/** - * Tests whether we get infinite loop if InputStream is already read (#10096) - */ public class FileUploadHandlerTest { private FileUploadHandler handler; - private VaadinRequest request; + @Mock private VaadinResponse response; + @Mock private StreamVariable streamVariable; + @Mock private ClientConnector clientConnector; + @Mock private VaadinRequest request; + @Mock private UI ui; + @Mock private ConnectorTracker connectorTracker; + @Mock private VaadinSession session; + @Mock private OutputStream responseOutput; + + private int uiId = 123; + private final String connectorId = "connectorId"; + private final String variableName = "name"; + private final String expectedSecurityKey = "key"; @Before public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + handler = new FileUploadHandler(); - InputStream inputStream = new InputStream() { - private int counter = 0; + + mockRequest(); + mockConnectorTracker(); + mockUi(); + + when(clientConnector.isConnectorEnabled()).thenReturn(true); + when(streamVariable.getOutputStream()).thenReturn(mock(OutputStream.class)); + when(response.getOutputStream()).thenReturn(responseOutput); + } + + private void mockConnectorTracker() { + when(connectorTracker.getSeckey(streamVariable)).thenReturn(expectedSecurityKey); + when(connectorTracker.getStreamVariable(connectorId, variableName)).thenReturn(streamVariable); + when(connectorTracker.getConnector(connectorId)).thenReturn(clientConnector); + } + + private void mockRequest() throws IOException { + when(request.getPathInfo()).thenReturn("/" + ServletPortletHelper.UPLOAD_URL_PREFIX + uiId + "/"+ connectorId + "/" + variableName + "/" + expectedSecurityKey); + when(request.getInputStream()).thenReturn(createInputStream("foobar")); + when(request.getHeader("Content-Length")).thenReturn("6"); + when(request.getContentType()).thenReturn("foobar"); + } + + private InputStream createInputStream(final String content) { + return new InputStream() { + int counter = 0; + byte[] msg = content.getBytes(); @Override public int read() throws IOException { - counter++; - if (counter > 6) { - throw new RuntimeException( - "-1 is ignored by FileUploadHandler"); + if(counter > msg.length + 1) { + throw new AssertionError("-1 was ignored by FileUploadHandler."); + } + + if(counter >= msg.length) { + counter++; + return -1; } - return -1; - } + return msg[counter++]; + } }; - request = Mockito.mock(VaadinRequest.class); - Mockito.when(request.getInputStream()).thenReturn(inputStream); - Mockito.when(request.getHeader("Content-Length")).thenReturn("211"); } + private void mockUi() { + when(ui.getConnectorTracker()).thenReturn(connectorTracker); + when(session.getUIById(uiId)).thenReturn(ui); + } + + /** + * Tests whether we get infinite loop if InputStream is already read (#10096) + */ @Test(expected = IOException.class) - public void testStreamEnded() throws IOException { + public void exceptionIsThrownOnUnexpectedEnd() throws IOException { + when(request.getInputStream()).thenReturn(createInputStream("")); + when(request.getHeader("Content-Length")).thenReturn("1"); + handler.doHandleSimpleMultipartFileUpload(null, request, null, null, null, null, null); + } + + @Test + public void responseIsSentOnCorrectSecurityKey() throws IOException { + when(connectorTracker.getSeckey(streamVariable)).thenReturn(expectedSecurityKey); + + handler.handleRequest(session, request, response); + verify(responseOutput).close(); } + @Test + public void responseIsNotSentOnIncorrectSecurityKey() throws IOException { + when(connectorTracker.getSeckey(streamVariable)).thenReturn("another key expected"); + + handler.handleRequest(session, request, response); + + verifyZeroInteractions(responseOutput); + } + + @Test + public void responseIsNotSentOnMissingSecurityKey() throws IOException { + when(connectorTracker.getSeckey(streamVariable)).thenReturn(null); + + handler.handleRequest(session, request, response); + + verifyZeroInteractions(responseOutput); + } } diff --git a/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java b/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java index f734d76633..6e81af97a3 100644 --- a/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java +++ b/server/tests/src/com/vaadin/tests/data/converter/StringToBooleanConverterTest.java @@ -4,9 +4,25 @@ import junit.framework.TestCase; import com.vaadin.data.util.converter.StringToBooleanConverter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + public class StringToBooleanConverterTest extends TestCase { StringToBooleanConverter converter = new StringToBooleanConverter(); + StringToBooleanConverter yesNoConverter = new StringToBooleanConverter("yes","no"); + StringToBooleanConverter localeConverter = new StringToBooleanConverter() { + @Override + public String getFalseString(Locale locale) { + return SimpleDateFormat.getDateInstance(SimpleDateFormat.LONG,locale).format(new Date(3000000000000L)); + } + + @Override + public String getTrueString(Locale locale) { + return SimpleDateFormat.getDateInstance(SimpleDateFormat.LONG,locale).format(new Date(2000000000000L)); + } + }; public void testNullConversion() { assertEquals(null, converter.convertToModel(null, Boolean.class, null)); @@ -20,4 +36,22 @@ public class StringToBooleanConverterTest extends TestCase { assertTrue(converter.convertToModel("true", Boolean.class, null)); assertFalse(converter.convertToModel("false", Boolean.class, null)); } + + public void testYesNoValueConversion() { + assertTrue(yesNoConverter.convertToModel("yes", Boolean.class, null)); + assertFalse(yesNoConverter.convertToModel("no", Boolean.class, null)); + + assertEquals("yes", yesNoConverter.convertToPresentation(true, String.class, null)); + assertEquals("no", yesNoConverter.convertToPresentation(false, String.class, null)); + } + + + public void testLocale() { + assertEquals("May 18, 2033", localeConverter.convertToPresentation(true, String.class, Locale.US)); + assertEquals("January 24, 2065", localeConverter.convertToPresentation(false, String.class, Locale.US)); + + assertEquals("18. Mai 2033", localeConverter.convertToPresentation(true, String.class, Locale.GERMANY)); + assertEquals("24. Januar 2065", localeConverter.convertToPresentation(false, String.class, Locale.GERMANY)); + } + } diff --git a/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java b/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java index a4c3732e1f..59c1ed133a 100644 --- a/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java +++ b/server/tests/src/com/vaadin/tests/data/converter/StringToEnumConverterTest.java @@ -1,13 +1,14 @@ package com.vaadin.tests.data.converter; -import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Test; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.Converter.ConversionException; import com.vaadin.data.util.converter.ReverseConverter; import com.vaadin.data.util.converter.StringToEnumConverter; -public class StringToEnumConverterTest extends TestCase { +public class StringToEnumConverterTest { public static enum FooEnum { VALUE1, SOME_VALUE, FOO_BAR_BAZ, Bar, nonStandardCase, _HUGH; @@ -17,56 +18,64 @@ public class StringToEnumConverterTest extends TestCase { Converter<Enum, String> reverseConverter = new ReverseConverter<Enum, String>( converter); + @Test public void testEmptyStringConversion() { - assertEquals(null, converter.convertToModel("", Enum.class, null)); + Assert.assertEquals(null, + converter.convertToModel("", Enum.class, null)); } + @Test public void testInvalidEnumClassConversion() { try { converter.convertToModel("Foo", Enum.class, null); - fail("No exception thrown"); + Assert.fail("No exception thrown"); } catch (ConversionException e) { // OK } } + @Test public void testNullConversion() { - assertEquals(null, converter.convertToModel(null, Enum.class, null)); + Assert.assertEquals(null, + converter.convertToModel(null, Enum.class, null)); } + @Test public void testReverseNullConversion() { - assertEquals(null, + Assert.assertEquals(null, reverseConverter.convertToModel(null, String.class, null)); } + @Test public void testValueConversion() { - assertEquals(FooEnum.VALUE1, + Assert.assertEquals(FooEnum.VALUE1, converter.convertToModel("Value1", FooEnum.class, null)); - assertEquals(FooEnum.SOME_VALUE, + Assert.assertEquals(FooEnum.SOME_VALUE, converter.convertToModel("Some value", FooEnum.class, null)); - assertEquals(FooEnum.FOO_BAR_BAZ, + Assert.assertEquals(FooEnum.FOO_BAR_BAZ, converter.convertToModel("Foo bar baz", FooEnum.class, null)); - assertEquals(FooEnum.Bar, + Assert.assertEquals(FooEnum.Bar, converter.convertToModel("Bar", FooEnum.class, null)); - assertEquals(FooEnum.nonStandardCase, converter.convertToModel( + Assert.assertEquals(FooEnum.nonStandardCase, converter.convertToModel( "Nonstandardcase", FooEnum.class, null)); - assertEquals(FooEnum._HUGH, + Assert.assertEquals(FooEnum._HUGH, converter.convertToModel("_hugh", FooEnum.class, null)); } + @Test public void testReverseValueConversion() { - assertEquals("Value1", reverseConverter.convertToModel(FooEnum.VALUE1, - String.class, null)); - assertEquals("Some value", reverseConverter.convertToModel( + Assert.assertEquals("Value1", reverseConverter.convertToModel( + FooEnum.VALUE1, String.class, null)); + Assert.assertEquals("Some value", reverseConverter.convertToModel( FooEnum.SOME_VALUE, String.class, null)); - assertEquals("Foo bar baz", reverseConverter.convertToModel( + Assert.assertEquals("Foo bar baz", reverseConverter.convertToModel( FooEnum.FOO_BAR_BAZ, String.class, null)); - assertEquals("Bar", reverseConverter.convertToModel(FooEnum.Bar, + Assert.assertEquals("Bar", reverseConverter.convertToModel(FooEnum.Bar, String.class, null)); - assertEquals("Nonstandardcase", reverseConverter.convertToModel( + Assert.assertEquals("Nonstandardcase", reverseConverter.convertToModel( FooEnum.nonStandardCase, String.class, null)); - assertEquals("_hugh", reverseConverter.convertToModel(FooEnum._HUGH, - String.class, null)); + Assert.assertEquals("_hugh", reverseConverter.convertToModel( + FooEnum._HUGH, String.class, null)); } diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java index acee3e2ca8..6510d8ad40 100644 --- a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java +++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java @@ -203,7 +203,7 @@ public class DesignFormatterTest { ShortcutAction action = new ShortcutAction("&^d"); String formatted = formatter.format(action); // note the space here - it separates key combination from caption - assertEquals("alt-ctrl-d d", formatted); + assertEquals("ctrl-alt-d d", formatted); ShortcutAction result = formatter .parse(formatted, ShortcutAction.class); @@ -215,7 +215,7 @@ public class DesignFormatterTest { ShortcutAction action = new ShortcutAction(null, KeyCode.D, new int[] { ModifierKey.ALT, ModifierKey.CTRL }); String formatted = formatter.format(action); - assertEquals("alt-ctrl-d", formatted); + assertEquals("ctrl-alt-d", formatted); ShortcutAction result = formatter .parse(formatted, ShortcutAction.class); diff --git a/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java b/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java index 9603032ce5..829ad0455d 100644 --- a/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java +++ b/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java @@ -73,7 +73,7 @@ public class ClassesSerializableTest extends TestCase { "com\\.vaadin\\.server\\.AbstractClientConnector\\$1\\$1", // "com\\.vaadin\\.server\\.JsonCodec\\$1", // "com\\.vaadin\\.server\\.communication\\.PushConnection", // - "com\\.vaadin\\.server\\.communication\\.AtmospherePushConnection", // + "com\\.vaadin\\.server\\.communication\\.AtmospherePushConnection.*", // "com\\.vaadin\\.util\\.ConnectorHelper", // "com\\.vaadin\\.server\\.VaadinSession\\$FutureAccess", // "com\\.vaadin\\.external\\..*", // diff --git a/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java index 51abf6be96..b1c10789b4 100644 --- a/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java +++ b/server/tests/src/com/vaadin/tests/server/component/button/ButtonDeclarativeTest.java @@ -98,7 +98,7 @@ public class ButtonDeclarativeTest extends DeclarativeTestBase<Button> { @Test public void testAttributes() { String design = "<v-button tabindex=3 plain-text='' icon-alt=OK " - + "click-shortcut=ctrl-shift-o></v-button>"; + + "click-shortcut=shift-ctrl-o></v-button>"; Button b = new Button(""); b.setTabIndex(3); b.setIconAlternateText("OK"); diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java b/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java deleted file mode 100644 index 9ecf131c5b..0000000000 --- a/server/tests/src/com/vaadin/tests/server/component/grid/DataProviderExtension.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.vaadin.tests.server.component.grid; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.Arrays; - -import org.junit.Before; -import org.junit.Test; - -import com.vaadin.data.Container; -import com.vaadin.data.Container.Indexed; -import com.vaadin.data.Item; -import com.vaadin.data.Property; -import com.vaadin.data.RpcDataProviderExtension; -import com.vaadin.data.RpcDataProviderExtension.DataProviderKeyMapper; -import com.vaadin.data.util.IndexedContainer; - -public class DataProviderExtension { - private RpcDataProviderExtension dataProvider; - private DataProviderKeyMapper keyMapper; - private Container.Indexed container; - - private static final Object ITEM_ID1 = "itemid1"; - private static final Object ITEM_ID2 = "itemid2"; - private static final Object ITEM_ID3 = "itemid3"; - - private static final Object PROPERTY_ID1_STRING = "property1"; - - @Before - public void setup() { - container = new IndexedContainer(); - populate(container); - - dataProvider = new RpcDataProviderExtension(container); - keyMapper = dataProvider.getKeyMapper(); - } - - private static void populate(Indexed container) { - container.addContainerProperty(PROPERTY_ID1_STRING, String.class, ""); - for (Object itemId : Arrays.asList(ITEM_ID1, ITEM_ID2, ITEM_ID3)) { - final Item item = container.addItem(itemId); - @SuppressWarnings("unchecked") - final Property<String> stringProperty = item - .getItemProperty(PROPERTY_ID1_STRING); - stringProperty.setValue(itemId.toString()); - } - } - - @Test - public void pinBasics() { - assertFalse("itemId1 should not start as pinned", - keyMapper.isPinned(ITEM_ID2)); - - keyMapper.pin(ITEM_ID1); - assertTrue("itemId1 should now be pinned", keyMapper.isPinned(ITEM_ID1)); - - keyMapper.unpin(ITEM_ID1); - assertFalse("itemId1 should not be pinned anymore", - keyMapper.isPinned(ITEM_ID2)); - } - - @Test(expected = IllegalStateException.class) - public void doublePinning() { - keyMapper.pin(ITEM_ID1); - keyMapper.pin(ITEM_ID1); - } - - @Test(expected = IllegalStateException.class) - public void nonexistentUnpin() { - keyMapper.unpin(ITEM_ID1); - } -} diff --git a/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java index 1d233af494..607fb471b9 100644 --- a/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java +++ b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java @@ -71,8 +71,9 @@ public class WindowDeclarativeTest extends DeclarativeTestBase<Window> { expected.setClosable(!expected.isClosable()); expected.setDraggable(!expected.isDraggable()); - expected.setCloseShortcut(KeyCode.ESCAPE, ModifierKey.CTRL, - ModifierKey.ALT); + expected.removeAllCloseShortcuts(); + expected.addCloseShortcut(KeyCode.ESCAPE, ModifierKey.ALT, + ModifierKey.CTRL); expected.setAssistivePrefix("Hello"); expected.setAssistivePostfix("World"); @@ -86,6 +87,40 @@ public class WindowDeclarativeTest extends DeclarativeTestBase<Window> { } @Test + public void testMultiCloseShortcuts() { + + Window expected = new Window(); + + // Add two shortcuts - should now contain three (default escape + two + // added) + expected.addCloseShortcut(KeyCode.SPACEBAR); + expected.addCloseShortcut(KeyCode.ARROW_LEFT, ModifierKey.ALT, + ModifierKey.CTRL); + + // Try to add the same shortcut again, should be no-op + expected.addCloseShortcut(KeyCode.ARROW_LEFT, ModifierKey.CTRL, + ModifierKey.ALT); + + // Add a third shortcut, should total four (default escape + three + // added) + expected.addCloseShortcut(KeyCode.ARROW_RIGHT, ModifierKey.CTRL); + + // Test validity + String design = "<v-window close-shortcut='escape spacebar ctrl-alt-left ctrl-right' />"; + testRead(design, expected); + testWrite(design, expected); + + // Try removing the spacebar shortcut + expected.removeCloseShortcut(KeyCode.SPACEBAR); + + // Test again + design = "<v-window close-shortcut='escape ctrl-alt-left ctrl-right' />"; + testRead(design, expected); + testWrite(design, expected); + + } + + @Test public void testInvalidPosition() { assertInvalidPosition(""); assertInvalidPosition("1"); diff --git a/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java b/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java index eb07fae07f..cea8df0ba6 100644 --- a/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java +++ b/server/tests/src/com/vaadin/tests/server/renderer/RendererTest.java @@ -15,8 +15,17 @@ */ package com.vaadin.tests.server.renderer; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import java.util.Date; +import java.util.Locale; + +import org.junit.Before; +import org.junit.Test; + import com.vaadin.data.Item; -import com.vaadin.data.RpcDataProviderExtension; import com.vaadin.data.util.IndexedContainer; import com.vaadin.data.util.converter.Converter; import com.vaadin.data.util.converter.StringToIntegerConverter; @@ -24,22 +33,15 @@ import com.vaadin.server.VaadinSession; import com.vaadin.tests.server.component.grid.TestGrid; import com.vaadin.tests.util.AlwaysLockedVaadinSession; import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.AbstractRenderer; import com.vaadin.ui.Grid.Column; import com.vaadin.ui.renderers.ButtonRenderer; import com.vaadin.ui.renderers.DateRenderer; import com.vaadin.ui.renderers.HtmlRenderer; import com.vaadin.ui.renderers.NumberRenderer; import com.vaadin.ui.renderers.TextRenderer; -import elemental.json.JsonValue; -import org.junit.Before; -import org.junit.Test; - -import java.util.Date; -import java.util.Locale; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; +import elemental.json.JsonValue; public class RendererTest { @@ -259,7 +261,7 @@ public class RendererTest { } private JsonValue render(Column column, Object value) { - return RpcDataProviderExtension.encodeValue(value, - column.getRenderer(), column.getConverter(), grid.getLocale()); + return AbstractRenderer.encodeValue(value, column.getRenderer(), + column.getConverter(), grid.getLocale()); } } diff --git a/shared/src/com/vaadin/shared/VBrowserDetails.java b/shared/src/com/vaadin/shared/VBrowserDetails.java index 561b6c76d0..d0de8ffb9f 100644 --- a/shared/src/com/vaadin/shared/VBrowserDetails.java +++ b/shared/src/com/vaadin/shared/VBrowserDetails.java @@ -41,6 +41,7 @@ public class VBrowserDetails implements Serializable { private boolean isFirefox = false; private boolean isOpera = false; private boolean isIE = false; + private boolean isEdge = false; private boolean isPhantomJS = false; private boolean isWindowsPhone; @@ -88,6 +89,16 @@ public class VBrowserDetails implements Serializable { isSafari = !isChrome && !isIE && userAgent.indexOf("safari") != -1; isFirefox = userAgent.indexOf(" firefox/") != -1; isPhantomJS = userAgent.indexOf("phantomjs/") != -1; + if (userAgent.indexOf(" edge/") != -1) { + isEdge = true; + isChrome = false; + isOpera = false; + isIE = false; + isSafari = false; + isFirefox = false; + isWebKit = false; + isGecko = false; + } // chromeframe isChromeFrameCapable = userAgent.indexOf("chromeframe") != -1; @@ -115,6 +126,8 @@ public class VBrowserDetails implements Serializable { tmp = tmp.replaceFirst("([0-9]+\\.[0-9]+).*", "$1"); browserEngineVersion = Float.parseFloat(tmp); } + } else if (isEdge) { + browserEngineVersion = 0; } } catch (Exception e) { // Browser engine version parsing failed @@ -158,6 +171,9 @@ public class VBrowserDetails implements Serializable { i = userAgent.indexOf("opera/") + 6; } parseVersionString(safeSubstring(userAgent, i, i + 5)); + } else if (isEdge) { + int i = userAgent.indexOf(" edge/") + 6; + parseVersionString(safeSubstring(userAgent, i, i + 8)); } } catch (Exception e) { // Browser version parsing failed @@ -373,6 +389,15 @@ public class VBrowserDetails implements Serializable { } /** + * Tests if the browser is Edge. + * + * @return true if it is Edge, false otherwise + */ + public boolean isEdge() { + return isEdge; + } + + /** * Tests if the browser is PhantomJS. * * @return true if it is PhantomJS, false otherwise diff --git a/shared/src/com/vaadin/shared/data/DataProviderRpc.java b/shared/src/com/vaadin/shared/data/DataProviderRpc.java index 4bfdb8b6c5..05965ea56c 100644 --- a/shared/src/com/vaadin/shared/data/DataProviderRpc.java +++ b/shared/src/com/vaadin/shared/data/DataProviderRpc.java @@ -91,4 +91,16 @@ public interface DataProviderRpc extends ClientRpc { * the size of the new data set */ public void resetDataAndSize(int size); + + /** + * Informs the client that rows have been updated. The client-side + * DataSource will map the given data to correct index if it should be in + * the cache. + * + * @since 7.6 + * @param rowArray + * array of updated rows + */ + @NoLayout + public void updateRowData(JsonArray rowArray); } diff --git a/shared/src/com/vaadin/shared/data/DataRequestRpc.java b/shared/src/com/vaadin/shared/data/DataRequestRpc.java index 0d9b919a4e..4b553dda68 100644 --- a/shared/src/com/vaadin/shared/data/DataRequestRpc.java +++ b/shared/src/com/vaadin/shared/data/DataRequestRpc.java @@ -16,10 +16,12 @@ package com.vaadin.shared.data; -import com.vaadin.shared.annotations.NoLoadingIndicator; import com.vaadin.shared.annotations.Delayed; +import com.vaadin.shared.annotations.NoLoadingIndicator; import com.vaadin.shared.communication.ServerRpc; +import elemental.json.JsonArray; + /** * RPC interface used for requesting container data to the client. * @@ -45,15 +47,13 @@ public interface DataRequestRpc extends ServerRpc { int firstCachedRowIndex, int cacheSize); /** - * Informs the server that an item referenced with a key pinned status has - * changed. This is a delayed call that happens along with next rpc call to - * server. + * Informs the server that items have been dropped from the client cache. * - * @param key - * key mapping to item - * @param isPinned - * pinned status of referenced item + * @since 7.6 + * @param rowKeys + * array of dropped keys mapping to items */ @Delayed - public void setPinned(String key, boolean isPinned); + @NoLoadingIndicator + public void dropRows(JsonArray rowKeys); } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java index 3c6d993482..ac1b1d5a78 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridClientRpc.java @@ -15,8 +15,6 @@ */ package com.vaadin.shared.ui.grid; -import java.util.Set; - import com.vaadin.shared.communication.ClientRpc; /** @@ -57,19 +55,4 @@ public interface GridClientRpc extends ClientRpc { * Command client Grid to recalculate column widths. */ public void recalculateColumnWidths(); - - /** - * Informs the GridConnector on how the indexing of details connectors has - * changed. - * - * @since 7.5.0 - * @param connectorChanges - * the indexing changes of details connectors - * @param fetchId - * the id of the request for fetching the changes. A negative - * number indicates a push (not requested by the client side) - */ - public void setDetailsConnectorChanges( - Set<DetailsConnectorChange> connectorChanges, int fetchId); - } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridConstants.java b/shared/src/com/vaadin/shared/ui/grid/GridConstants.java index 0606e4b1cc..5b2ac96975 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridConstants.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridConstants.java @@ -74,4 +74,19 @@ public final class GridConstants implements Serializable { /** The default cancel button caption in the editor */ public static final String DEFAULT_CANCEL_CAPTION = "Cancel"; + + /** + * Event ID constant for editor open event + */ + public static final String EDITOR_OPEN_EVENT_ID = "editorOpen"; + + /** + * Event ID constant for editor move event + */ + public static final String EDITOR_MOVE_EVENT_ID = "editorMove"; + + /** + * Event ID constant for editor close event + */ + public static final String EDITOR_CLOSE_EVENT_ID = "editorClose"; } diff --git a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java index dca55c11c4..8d64794b41 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridServerRpc.java @@ -29,14 +29,35 @@ import com.vaadin.shared.data.sort.SortDirection; */ public interface GridServerRpc extends ServerRpc { - void select(List<String> newSelection); - - void selectAll(); - void sort(String[] columnIds, SortDirection[] directions, boolean userOriginated); /** + * Informs the server that the editor was opened (fresh) on a certain row + * + * @param rowKey + * a key identifying item the editor was opened on + */ + void editorOpen(String rowKey); + + /** + * Informs the server that the editor was reopened (without closing it in + * between) on another row + * + * @param rowKey + * a key identifying item the editor was opened on + */ + void editorMove(String rowKey); + + /** + * Informs the server that the editor was closed + * + * @param rowKey + * a key identifying item the editor was opened on + */ + void editorClose(String rowKey); + + /** * Informs the server that an item has been clicked in Grid. * * @param rowKey @@ -61,23 +82,6 @@ public interface GridServerRpc extends ServerRpc { List<String> oldColumnOrder); /** - * This is a trigger for Grid to send whatever has changed regarding the - * details components. - * <p> - * The components can't be sent eagerly, since they are generated as a side - * effect in - * {@link com.vaadin.data.RpcDataProviderExtension#beforeClientResponse(boolean)} - * , and that is too late to change the hierarchy. So we need this - * round-trip to work around that limitation. - * - * @since 7.5.0 - * @param fetchId - * an unique identifier for the request - * @see com.vaadin.ui.Grid#setDetailsVisible(Object, boolean) - */ - void sendDetailsComponents(int fetchId); - - /** * Informs the server that the column's visibility has been changed. * * @since 7.5.0 diff --git a/shared/src/com/vaadin/shared/ui/grid/GridState.java b/shared/src/com/vaadin/shared/ui/grid/GridState.java index c23c931683..e51b1a78a7 100644 --- a/shared/src/com/vaadin/shared/ui/grid/GridState.java +++ b/shared/src/com/vaadin/shared/ui/grid/GridState.java @@ -19,9 +19,9 @@ package com.vaadin.shared.ui.grid; import java.util.ArrayList; import java.util.List; -import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.annotations.DelegateToWidget; import com.vaadin.shared.data.sort.SortDirection; +import com.vaadin.shared.ui.TabIndexState; /** * The shared state for the {@link com.vaadin.ui.components.grid.Grid} component @@ -29,7 +29,7 @@ import com.vaadin.shared.data.sort.SortDirection; * @since 7.4 * @author Vaadin Ltd */ -public class GridState extends AbstractComponentState { +public class GridState extends TabIndexState { /** * A description of which of the three bundled SelectionModels is currently @@ -103,6 +103,20 @@ public class GridState extends AbstractComponentState { public static final String JSONKEY_CELLSTYLES = "cs"; /** + * The key in which a row's description can be found + * + * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, String) + */ + public static final String JSONKEY_ROWDESCRIPTION = "rd"; + + /** + * The key in which a cell's description can be found + * + * @see com.vaadin.shared.data.DataProviderRpc#setRowData(int, String) + */ + public static final String JSONKEY_CELLDESCRIPTION = "cd"; + + /** * The key that tells whether details are visible for the row. * * @since 7.5.0 @@ -115,6 +129,13 @@ public class GridState extends AbstractComponentState { public static final String JSONKEY_DETAILS_VISIBLE = "dv"; /** + * The key that tells whether row is selected. + * + * @since + */ + public static final String JSONKEY_SELECTED = "s"; + + /** * Columns in grid. */ public List<GridColumnState> columns = new ArrayList<GridColumnState>(); @@ -139,14 +160,6 @@ public class GridState extends AbstractComponentState { @DelegateToWidget public HeightMode heightMode = HeightMode.CSS; - // instantiated just to avoid NPEs - public List<String> selectedKeys = new ArrayList<String>(); - - public SharedSelectionMode selectionMode; - - /** Whether single select mode can be cleared through the UI */ - public boolean singleSelectDeselectAllowed = true; - /** Keys of the currently sorted columns */ public String[] sortColumns = new String[0]; @@ -156,10 +169,12 @@ public class GridState extends AbstractComponentState { /** The enabled state of the editor interface */ public boolean editorEnabled = false; - /** Whether row data might contain generated row styles */ - public boolean hasRowStyleGenerator; - /** Whether row data might contain generated cell styles */ - public boolean hasCellStyleGenerator; + /** Buffered editor mode */ + @DelegateToWidget + public boolean editorBuffered = true; + + /** Whether rows and/or cells have generated descriptions (tooltips) */ + public boolean hasDescriptions; /** The caption for the save button in the editor */ @DelegateToWidget diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java new file mode 100644 index 0000000000..e7324552f4 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelServerRpc.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import java.util.List; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * ServerRpc for MultiSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public interface MultiSelectionModelServerRpc extends ServerRpc { + + /** + * Select a list of rows based on their row keys on the server-side. + * + * @param rowKeys + * list of row keys to select + */ + public void select(List<String> rowKeys); + + /** + * Deselect a list of rows based on their row keys on the server-side. + * + * @param rowKeys + * list of row keys to deselect + */ + public void deselect(List<String> rowKeys); + + /** + * Selects all rows on the server-side. + */ + public void selectAll(); + + /** + * Deselects all rows on the server-side. + */ + public void deselectAll(); +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java new file mode 100644 index 0000000000..3ffd0d0f93 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/MultiSelectionModelState.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import com.vaadin.shared.communication.SharedState; + +/** + * SharedState object for MultiSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public class MultiSelectionModelState extends SharedState { + + /* Select All -checkbox status */ + public boolean allSelected; + +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java new file mode 100644 index 0000000000..3e2a8ec847 --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelServerRpc.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import com.vaadin.shared.communication.ServerRpc; + +/** + * ServerRpc for SingleSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public interface SingleSelectionModelServerRpc extends ServerRpc { + + /** + * Selects a row on server-side. + * + * @param rowKey + * row key of selected row; {@code null} if deselect + */ + public void select(String rowKey); +} diff --git a/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java new file mode 100644 index 0000000000..719b60b2ba --- /dev/null +++ b/shared/src/com/vaadin/shared/ui/grid/selection/SingleSelectionModelState.java @@ -0,0 +1,30 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.shared.ui.grid.selection; + +import com.vaadin.shared.communication.SharedState; + +/** + * SharedState object for SingleSelectionModel. + * + * @since + * @author Vaadin Ltd + */ +public class SingleSelectionModelState extends SharedState { + + /* Allow deselecting rows */ + public boolean deselectAllowed; +} diff --git a/shared/src/com/vaadin/shared/ui/table/TableConstants.java b/shared/src/com/vaadin/shared/ui/table/TableConstants.java index fd1c61c772..e782492e9d 100644 --- a/shared/src/com/vaadin/shared/ui/table/TableConstants.java +++ b/shared/src/com/vaadin/shared/ui/table/TableConstants.java @@ -23,6 +23,7 @@ public class TableConstants implements Serializable { public static final String FOOTER_CLICK_EVENT_ID = "handleFooterClick"; public static final String COLUMN_RESIZE_EVENT_ID = "columnResize"; public static final String COLUMN_REORDER_EVENT_ID = "columnReorder"; + public static final String COLUMN_COLLAPSE_EVENT_ID = "columnCollapse"; @Deprecated public static final String ATTRIBUTE_PAGEBUFFER_FIRST = "pb-ft"; diff --git a/uitest/integration_tests.xml b/uitest/integration_tests.xml index 395627a4a7..20d0d2147d 100644 --- a/uitest/integration_tests.xml +++ b/uitest/integration_tests.xml @@ -411,7 +411,8 @@ <antcall target="integration-test-osgi" /> <antcall target="integration-test-tomcat7apacheproxy" /> <antcall target="integration-test-websphere8" /> - <antcall target="integration-test-websphereportal8" /> + <!-- Currently the test system does not have a server for automatic testing of WebSphere Portal 8 --> + <!-- <antcall target="integration-test-websphereportal8" /> --> </parallel> </target> diff --git a/uitest/ivy.xml b/uitest/ivy.xml index cb2d5cff45..e17e094f79 100644 --- a/uitest/ivy.xml +++ b/uitest/ivy.xml @@ -31,7 +31,7 @@ rev="4.2.0.Final" conf="build,ide -> default" /> <!-- Google App Engine --> <dependency org="com.google.appengine" name="appengine-api-1.0-sdk" - rev="1.2.1" conf="build-provided,ide -> default" /> + rev="1.7.7" conf="build-provided,ide -> default" /> <!-- LIBRARY DEPENDENCIES (compile time) --> <!-- Project modules --> diff --git a/uitest/src/com/vaadin/tests/VerifyBrowserVersionTest.java b/uitest/src/com/vaadin/tests/VerifyBrowserVersionTest.java index c6ccd1bf4c..ed0f1a9b4f 100644 --- a/uitest/src/com/vaadin/tests/VerifyBrowserVersionTest.java +++ b/uitest/src/com/vaadin/tests/VerifyBrowserVersionTest.java @@ -40,7 +40,7 @@ public class VerifyBrowserVersionTest extends MultiBrowserTest { // Chrome version does not necessarily match the desired version // because of auto updates... browserIdentifier = getExpectedUserAgentString(getDesiredCapabilities()) - + "43"; + + "44"; } else { browserIdentifier = getExpectedUserAgentString(desiredCapabilities) + desiredCapabilities.getVersion(); diff --git a/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.java b/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.java index ddef40b2d0..c8fc96f596 100644 --- a/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.java +++ b/uitest/src/com/vaadin/tests/application/VaadinSessionAttribute.java @@ -35,9 +35,13 @@ public class VaadinSessionAttribute extends AbstractTestUI { new Button.ClickListener() { @Override public void buttonClick(ClickEvent event) { - Notification.show(getSession().getAttribute(ATTR_NAME) - + " & " - + getSession().getAttribute(Integer.class)); + Notification notification = new Notification( + getSession().getAttribute(ATTR_NAME) + + " & " + + getSession().getAttribute( + Integer.class)); + notification.setDelayMsec(Notification.DELAY_FOREVER); + notification.show(getPage()); } })); } diff --git a/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java b/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java index b289279b86..aca617aa5a 100644 --- a/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java +++ b/uitest/src/com/vaadin/tests/components/AbstractComponentTest.java @@ -20,6 +20,7 @@ import com.vaadin.server.ThemeResource; import com.vaadin.tests.util.Log; import com.vaadin.tests.util.LoremIpsum; import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.Component.Focusable; import com.vaadin.ui.MenuBar; import com.vaadin.ui.MenuBar.MenuItem; import com.vaadin.ui.themes.BaseTheme; @@ -242,16 +243,18 @@ public abstract class AbstractComponentTest<T extends AbstractComponent> createStyleNameSelect(CATEGORY_DECORATIONS); + createFocusActions(); } protected Command<T, Boolean> focusListenerCommand = new Command<T, Boolean>() { @Override public void execute(T c, Boolean value, Object data) { + FocusNotifier fn = (FocusNotifier) c; if (value) { - ((FocusNotifier) c).addListener(AbstractComponentTest.this); + fn.addFocusListener(AbstractComponentTest.this); } else { - ((FocusNotifier) c).removeListener(AbstractComponentTest.this); + fn.removeFocusListener(AbstractComponentTest.this); } } }; @@ -259,10 +262,11 @@ public abstract class AbstractComponentTest<T extends AbstractComponent> @Override public void execute(T c, Boolean value, Object data) { + BlurNotifier bn = (BlurNotifier) c; if (value) { - ((BlurNotifier) c).addListener(AbstractComponentTest.this); + bn.addBlurListener(AbstractComponentTest.this); } else { - ((BlurNotifier) c).removeListener(AbstractComponentTest.this); + bn.removeBlurListener(AbstractComponentTest.this); } } }; @@ -279,6 +283,35 @@ public abstract class AbstractComponentTest<T extends AbstractComponent> } + private void createFocusActions() { + if (FocusNotifier.class.isAssignableFrom(getTestClass())) { + createFocusListener(CATEGORY_LISTENERS); + } + if (BlurNotifier.class.isAssignableFrom(getTestClass())) { + createBlurListener(CATEGORY_LISTENERS); + } + if (Focusable.class.isAssignableFrom(getTestClass())) { + LinkedHashMap<String, Integer> tabIndexes = new LinkedHashMap<String, Integer>(); + tabIndexes.put("0", 0); + tabIndexes.put("-1", -1); + tabIndexes.put("10", 10); + createSelectAction("Tab index", "State", tabIndexes, "0", + new Command<T, Integer>() { + @Override + public void execute(T c, Integer tabIndex, Object data) { + ((Focusable) c).setTabIndex(tabIndex); + } + }); + + createClickAction("Set focus", "State", new Command<T, Void>() { + @Override + public void execute(T c, Void value, Object data) { + ((Focusable) c).focus(); + } + }, null); + } + } + private void createStyleNameSelect(String category) { LinkedHashMap<String, String> options = new LinkedHashMap<String, String>(); options.put("-", null); diff --git a/uitest/src/com/vaadin/tests/components/OutOfSync.java b/uitest/src/com/vaadin/tests/components/OutOfSync.java new file mode 100644 index 0000000000..8cefffc9d1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/OutOfSync.java @@ -0,0 +1,55 @@ +package com.vaadin.tests.components; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Notification; + +public class OutOfSync extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + Button b = new Button("Click me after 1s to be out of sync"); + b.addClickListener(new ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + Notification.show("This code will never be reached"); + } + }); + setContent(b); + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // Remove button but prevent repaint -> causes out of sync + // issues + getSession().lock(); + try { + setContent(null); + getConnectorTracker().markClean(OutOfSync.this); + } finally { + getSession().unlock(); + } + } + }); + t.start(); + } + + @Override + protected String getTestDescription() { + return "Click the button after 1s when it has been removed server side (causing synchronization problems)"; + } + + @Override + protected Integer getTicketNumber() { + return 10780; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/OutOfSyncTest.java b/uitest/src/com/vaadin/tests/components/OutOfSyncTest.java index 0efb519e8d..c6bab3c9b9 100644 --- a/uitest/src/com/vaadin/tests/components/OutOfSyncTest.java +++ b/uitest/src/com/vaadin/tests/components/OutOfSyncTest.java @@ -1,55 +1,48 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ package com.vaadin.tests.components; -import com.vaadin.server.VaadinRequest; -import com.vaadin.ui.Button; -import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Button.ClickListener; -import com.vaadin.ui.Notification; - -public class OutOfSyncTest extends AbstractTestUI { - - @Override - protected void setup(VaadinRequest request) { - Button b = new Button("Click me after 1s to be out of sync"); - b.addClickListener(new ClickListener() { - - @Override - public void buttonClick(ClickEvent event) { - Notification.show("This code will never be reached"); - } - }); - setContent(b); - Thread t = new Thread(new Runnable() { - - @Override - public void run() { - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // Remove button but prevent repaint -> causes out of sync - // issues - getSession().lock(); - try { - setContent(null); - getConnectorTracker().markClean(OutOfSyncTest.this); - } finally { - getSession().unlock(); - } - } - }); - t.start(); - } +import org.junit.Assert; +import org.junit.Test; - @Override - protected String getTestDescription() { - return "Click the button after 1s when it has been removed server side (causing synchronization problems)"; - } +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class OutOfSyncTest extends MultiBrowserTest { + + @Test + public void testClientResync() throws InterruptedException { + openTestURL(); + + // Wait for server to get rid of the Button + sleep(1000); + + // On the first round-trip after the component has been removed, the + // server assumes the client will remove the button. How ever (to force + // it to be out of sync) the test UI calls markClean() on the Button to + // make it not update with the response. + $(ButtonElement.class).first().click(); + Assert.assertTrue( + "Button should not have disappeared on the first click.", + $(ButtonElement.class).exists()); - @Override - protected Integer getTicketNumber() { - return 10780; + // Truly out of sync, full resync is forced. + $(ButtonElement.class).first().click(); + Assert.assertFalse("Button should disappear with the second click.", + $(ButtonElement.class).exists()); } } diff --git a/uitest/src/com/vaadin/tests/components/abstractembedded/EmbeddedWithNullSourceTest.java b/uitest/src/com/vaadin/tests/components/abstractembedded/EmbeddedWithNullSourceTest.java new file mode 100644 index 0000000000..649ee42986 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/abstractembedded/EmbeddedWithNullSourceTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.abstractembedded; + +import java.io.IOException; +import java.util.List; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.remote.DesiredCapabilities; + +import com.vaadin.testbench.parallel.Browser; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class EmbeddedWithNullSourceTest extends MultiBrowserTest { + + @Override + public List<DesiredCapabilities> getBrowsersToTest() { + // No Flash on PhantomJS, IE 11 has a timeout issue, looks like a + // IEDriver problem, not reproduced running locally. + return getBrowserCapabilities(Browser.IE8, Browser.IE9, Browser.IE10, + Browser.CHROME, Browser.FIREFOX); + } + + @Test + public void testEmbeddedWithNullSource() throws IOException { + openTestURL(); + + waitForElementPresent(By.className("v-image")); + + compareScreen("nullSources"); + } +} diff --git a/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java b/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java index 692ca25b07..496a44a6c1 100644 --- a/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java +++ b/uitest/src/com/vaadin/tests/components/abstractfield/AbstractFieldTest.java @@ -13,8 +13,6 @@ import com.vaadin.data.Property; import com.vaadin.data.Property.ReadOnlyStatusChangeEvent; import com.vaadin.data.Property.ReadOnlyStatusChangeListener; import com.vaadin.data.Property.ValueChangeListener; -import com.vaadin.event.FieldEvents.BlurNotifier; -import com.vaadin.event.FieldEvents.FocusNotifier; import com.vaadin.tests.components.AbstractComponentTest; import com.vaadin.ui.AbstractField; import com.vaadin.ui.MenuBar; @@ -29,15 +27,9 @@ public abstract class AbstractFieldTest<T extends AbstractField> extends @Override protected void createActions() { super.createActions(); + createBooleanAction("Required", CATEGORY_STATE, false, requiredCommand); createRequiredErrorSelect(CATEGORY_DECORATIONS); - if (FocusNotifier.class.isAssignableFrom(getTestClass())) { - createFocusListener(CATEGORY_LISTENERS); - } - - if (BlurNotifier.class.isAssignableFrom(getTestClass())) { - createBlurListener(CATEGORY_LISTENERS); - } createValueChangeListener(CATEGORY_LISTENERS); createReadOnlyStatusChangeListener(CATEGORY_LISTENERS); @@ -52,7 +44,6 @@ public abstract class AbstractFieldTest<T extends AbstractField> extends // * invalidallowed // * error indicator // - // * tabindex // * validation visible // * ShortcutListener diff --git a/uitest/src/com/vaadin/tests/components/button/Buttons2.java b/uitest/src/com/vaadin/tests/components/button/Buttons2.java index 7526e7dbc3..4f75dfbfef 100644 --- a/uitest/src/com/vaadin/tests/components/button/Buttons2.java +++ b/uitest/src/com/vaadin/tests/components/button/Buttons2.java @@ -42,9 +42,6 @@ public class Buttons2<T extends Button> extends AbstractComponentTest<T> protected void createActions() { super.createActions(); - createFocusListener(CATEGORY_LISTENERS); - createBlurListener(CATEGORY_LISTENERS); - createBooleanAction("Disable on click", CATEGORY_FEATURES, false, disableOnClickCommand); addClickListener(CATEGORY_LISTENERS); diff --git a/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandler.java b/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandler.java index c2dfdb26c1..40dd43abb2 100644 --- a/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandler.java +++ b/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandler.java @@ -5,17 +5,27 @@ import java.text.SimpleDateFormat; import java.util.Locale; import com.vaadin.server.VaadinRequest; -import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.components.AbstractTestUIWithLog; import com.vaadin.ui.Calendar; import com.vaadin.ui.components.calendar.CalendarComponentEvents; +import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventClick; +import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventClickHandler; import com.vaadin.ui.components.calendar.event.BasicEvent; -public class NullEventMoveHandler extends AbstractTestUI { +public class NullEventMoveHandler extends AbstractTestUIWithLog { @Override protected void setup(VaadinRequest request) { Calendar calendar = getCalendar(); calendar.setHandler((CalendarComponentEvents.EventMoveHandler) null); + calendar.setHandler(new EventClickHandler() { + + @Override + public void eventClick(EventClick event) { + log("Clicked on " + event.getCalendarEvent().getCaption()); + + } + }); addComponent(calendar); } diff --git a/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandlerTest.java b/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandlerTest.java index c40cd9ce97..156100310c 100644 --- a/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandlerTest.java +++ b/uitest/src/com/vaadin/tests/components/calendar/NullEventMoveHandlerTest.java @@ -3,6 +3,7 @@ package com.vaadin.tests.components.calendar; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; +import org.junit.Assert; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; @@ -23,11 +24,25 @@ public class NullEventMoveHandlerTest extends DndActionsTest { } @Test + public void eventIsClickableWhenNotMovableInMonthView() { + getEvent().click(); + Assert.assertEquals("1. Clicked on foo", getLogRow(0)); + } + + @Test public void eventIsNotMovableInWeekView() { openWeekView(); assertEventCannotBeMoved(); } + @Test + public void eventIsClickableWhenNotMovableInWeekView() { + openWeekView(); + getEvent().findElement(By.className("v-calendar-event-caption")) + .click(); + Assert.assertEquals("1. Clicked on foo", getLogRow(0)); + } + private void assertEventCannotBeMoved() { int originalPosition = getEventXPosition(); diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboBoxEmptyItemsKeyboardNavigation.java b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxEmptyItemsKeyboardNavigation.java new file mode 100644 index 0000000000..2f96724db1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxEmptyItemsKeyboardNavigation.java @@ -0,0 +1,15 @@ +package com.vaadin.tests.components.combobox; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.ComboBox; + +public class ComboBoxEmptyItemsKeyboardNavigation extends AbstractTestUI { + @Override + protected void setup(VaadinRequest request) { + ComboBox comboBox = new ComboBox(); + comboBox.addItems("foo", "bar"); + + addComponent(comboBox); + } +} diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboBoxEmptyItemsKeyboardNavigationTest.java b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxEmptyItemsKeyboardNavigationTest.java new file mode 100644 index 0000000000..c5cbc5eea6 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxEmptyItemsKeyboardNavigationTest.java @@ -0,0 +1,30 @@ +package com.vaadin.tests.components.combobox; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyCollection.empty; + +import java.util.List; + +import org.junit.Test; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.By; +import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.tests.tb3.newelements.ComboBoxElement; + +public class ComboBoxEmptyItemsKeyboardNavigationTest extends MultiBrowserTest { + + @Test + public void navigatingUpOnAnEmptyMenuDoesntThrowErrors() { + setDebug(true); + openTestURL(); + + ComboBoxElement combobox = $(ComboBoxElement.class).first(); + combobox.sendKeys("a", Keys.ARROW_UP); + + List<WebElement> errors = findElements(By.className("SEVERE")); + + assertThat(errors, empty()); + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboBoxLargeIconsTest.java b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxLargeIconsTest.java new file mode 100644 index 0000000000..407ab7aa04 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/ComboBoxLargeIconsTest.java @@ -0,0 +1,58 @@ +package com.vaadin.tests.components.combobox; + +import org.junit.Test; +import org.openqa.selenium.Keys; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.testbench.By; +import com.vaadin.testbench.elements.NativeSelectElement; +import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.tests.tb3.newelements.ComboBoxElement; + +public class ComboBoxLargeIconsTest extends MultiBrowserTest { + @Override + protected Class<?> getUIClass() { + return com.vaadin.tests.components.combobox.Comboboxes.class; + } + + @Test + public void testComboBoxIcons() throws Exception { + openTestURL(); + NativeSelectElement iconSelect = $(NativeSelectElement.class).first(); + iconSelect.selectByText("16x16"); + + ComboBoxElement cb = $(ComboBoxElement.class).caption( + "Undefined wide select with 50 items").first(); + cb.openPopup(); + compareScreen("icons-16x16-page1"); + cb.openNextPage(); + compareScreen("icons-16x16-page2"); + cb.findElement(By.vaadin("#popup/item0")).click(); + compareScreen("icons-16x16-selected-1-3-5-9"); + + iconSelect.selectByText("32x32"); + cb.openPopup(); + compareScreen("icons-32x32-page2"); + + // Closes the popup + cb.openPopup(); + + iconSelect.selectByText("64x64"); + + ComboBoxElement pageLength0cb = $(ComboBoxElement.class).caption( + "Pagelength 0").first(); + pageLength0cb.openPopup(); + pageLength0cb.findElement(By.vaadin("#popup/item1")).click(); + + ComboBoxElement cb200px = $(ComboBoxElement.class).caption( + "200px wide select with 50 items").first(); + cb200px.openPopup(); + cb200px.findElement(By.vaadin("#popup/item1")).click(); + + ComboBoxElement cb150px = $(ComboBoxElement.class).caption( + "150px wide select with 5 items").first(); + new Actions(driver).sendKeys(cb150px, Keys.DOWN).perform(); + + compareScreen("icons-64x64-page1-highlight-first"); + } +} diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboboxPopupScrolling.java b/uitest/src/com/vaadin/tests/components/combobox/ComboboxPopupScrolling.java new file mode 100644 index 0000000000..9f1c4b9e03 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/ComboboxPopupScrolling.java @@ -0,0 +1,40 @@ +package com.vaadin.tests.components.combobox; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.HorizontalLayout; + +@Theme("valo") +public class ComboboxPopupScrolling extends AbstractTestUIWithLog { + @Override + protected void setup(VaadinRequest request) { + ComboBox combobox = new ComboBox("100px wide combobox"); + combobox.setWidth("100px"); + combobox.addItem("AMERICAN SAMOA"); + combobox.addItem("ANTIGUA AND BARBUDA"); + + ComboBox combobox2 = new ComboBox("250px wide combobox"); + combobox2.setWidth("250px"); + combobox2.addItem("AMERICAN SAMOA"); + combobox2.addItem("ANTIGUA AND BARBUDA"); + + ComboBox combobox3 = new ComboBox("Undefined wide combobox"); + combobox3.setWidth(null); + combobox3.addItem("AMERICAN SAMOA"); + combobox3.addItem("ANTIGUA AND BARBUDA"); + + ComboBox combobox4 = new ComboBox("Another 100px wide combobox"); + combobox4.setWidth("100px"); + for (int i = 0; i < 10; i++) { + combobox4.addItem("AMERICAN SAMOA " + i); + combobox4.addItem("ANTIGUA AND BARBUDA " + i); + } + + HorizontalLayout hl = new HorizontalLayout(combobox, combobox2, + combobox3, combobox4); + addComponent(hl); + } + +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/combobox/ComboboxPopupScrollingTest.java b/uitest/src/com/vaadin/tests/components/combobox/ComboboxPopupScrollingTest.java new file mode 100644 index 0000000000..ec5bc088da --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/ComboboxPopupScrollingTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.combobox; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class ComboboxPopupScrollingTest extends MultiBrowserTest { + + @Test + public void testNoScrollbarsValo() { + testNoScrollbars("valo"); + } + + @Test + public void testNoScrollbarsChameleon() { + testNoScrollbars("chameleon"); + } + + @Test + public void testNoScrollbarsRuno() { + testNoScrollbars("runo"); + } + + @Test + public void testNoScrollbarsReindeer() { + testNoScrollbars("reindeer"); + } + + private void testNoScrollbars(String theme) { + openTestURL("theme=" + theme); + + for (CustomComboBoxElement cb : $(CustomComboBoxElement.class).all()) { + String caption = cb.getCaption(); + cb.openPopup(); + WebElement popup = cb.getSuggestionPopup(); + WebElement scrollable = popup.findElement(By + .className("v-filterselect-suggestmenu")); + assertNoHorizontalScrollbar(scrollable, caption); + assertNoVerticalScrollbar(scrollable, caption); + } + } + +} diff --git a/uitest/src/com/vaadin/tests/components/combobox/CustomComboBoxElement.java b/uitest/src/com/vaadin/tests/components/combobox/CustomComboBoxElement.java new file mode 100644 index 0000000000..697d5eb932 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/combobox/CustomComboBoxElement.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.combobox; + +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.By; +import com.vaadin.testbench.elements.ComboBoxElement; +import com.vaadin.testbench.elementsbase.ServerClass; + +@ServerClass("com.vaadin.ui.ComboBox") +public class CustomComboBoxElement extends ComboBoxElement { + private static org.openqa.selenium.By bySuggestionPopup = By + .vaadin("#popup"); + + public WebElement getSuggestionPopup() { + ensurePopupOpen(); + return findElement(bySuggestionPopup); + } + + private void ensurePopupOpen() { + if (!isElementPresent(bySuggestionPopup)) { + openPopup(); + } + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java b/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java index 7d706ecd30..d5e01a9de8 100644 --- a/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/CustomRendererTest.java @@ -47,8 +47,8 @@ public class CustomRendererTest extends MultiBrowserTest { .getText()); grid.getCell(0, 1).click(); - assertEquals("row: 0, key: 0", grid.getCell(0, 1).getText()); - assertEquals("key: 0, itemId: " + CustomRenderer.ITEM_ID, + assertEquals("row: 0, key: 1", grid.getCell(0, 1).getText()); + assertEquals("key: 1, itemId: " + CustomRenderer.ITEM_ID, findDebugLabel().getText()); } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java new file mode 100644 index 0000000000..008c24cdd3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModel.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.widgetset.TestingWidgetSet; +import com.vaadin.ui.Grid.MultiSelectionModel; + +@Widgetset(TestingWidgetSet.NAME) +public class GridCustomSelectionModel extends AbstractTestUI { + + public static class MySelectionModel extends MultiSelectionModel { + } + + @Override + protected void setup(VaadinRequest request) { + PersonTestGrid grid = new PersonTestGrid(500); + grid.setSelectionModel(new MySelectionModel()); + addComponent(grid); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java new file mode 100644 index 0000000000..976e1e78fe --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridCustomSelectionModelTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.elements.GridElement.GridCellElement; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.tb3.MultiBrowserTest; + +@TestCategory("grid") +public class GridCustomSelectionModelTest extends MultiBrowserTest { + + @Test + public void testCustomSelectionModel() { + setDebug(true); + openTestURL(); + + GridElement grid = $(GridElement.class).first(); + GridCellElement cell = grid.getCell(0, 0); + assertTrue("First column of Grid should not have an input element", + cell.findElements(By.className("input")).isEmpty()); + + assertFalse("Row should not be selected initially", grid.getRow(0) + .isSelected()); + + cell.click(5, 5); + assertTrue("Click should select row", grid.getRow(0).isSelected()); + cell.click(5, 5); + assertFalse("Click should deselect row", grid.getRow(0).isSelected()); + + grid.sendKeys(Keys.SPACE); + assertTrue("Space should select row", grid.getRow(0).isSelected()); + grid.sendKeys(Keys.SPACE); + assertFalse("Space should deselect row", grid.getRow(0).isSelected()); + + assertNoErrorNotifications(); + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java index 1032378a2d..3d7f6da587 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetach.java @@ -56,7 +56,6 @@ public class GridDetailsDetach extends AbstractTestUI { layout.addComponent(new Button("Reattach Grid", new Button.ClickListener() { - @Override public void buttonClick(ClickEvent event) { gridContainer.removeAllComponents(); diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java index fc79fd1b68..7406daeacd 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsDetachTest.java @@ -70,4 +70,28 @@ public class GridDetailsDetachTest extends MultiBrowserTest { Assert.assertEquals("Spacer content not visible", "Extra data for Bean 5", spacers.get(1).getText()); } + + @Test + public void testDetachAndImmediateReattach() { + setDebug(true); + openTestURL(); + + $(GridElement.class).first().getCell(3, 0).click(); + $(GridElement.class).first().getCell(5, 0).click(); + + assertNoErrorNotifications(); + + // Detach and Re-attach Grid + $(ButtonElement.class).get(1).click(); + + assertNoErrorNotifications(); + + List<WebElement> spacers = findElements(By.className("v-grid-spacer")); + Assert.assertEquals("Not enough spacers in DOM", 2, spacers.size()); + Assert.assertEquals("Spacer content not visible", + "Extra data for Bean 3", spacers.get(0).getText()); + Assert.assertEquals("Spacer content not visible", + "Extra data for Bean 5", spacers.get(1).getText()); + } + } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java index 33f66d35be..a395d7e721 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridDetailsLocationTest.java @@ -86,9 +86,7 @@ public class GridDetailsLocationTest extends MultiBrowserTest { for (int rowIndex : params) { - data.add(new Param(rowIndex, false, false)); data.add(new Param(rowIndex, true, false)); - data.add(new Param(rowIndex, false, true)); data.add(new Param(rowIndex, true, true)); } @@ -138,23 +136,6 @@ public class GridDetailsLocationTest extends MultiBrowserTest { } @Test - public void testDetailsHeightWithNoGenerator() { - openTestURL(); - toggleAndScroll(5); - - verifyDetailsRowHeight(5, detailsDefaultHeight, 0); - verifyDetailsDecoratorLocation(5, 0, 0); - - toggleAndScroll(0); - - verifyDetailsRowHeight(0, detailsDefaultHeight, 0); - verifyDetailsDecoratorLocation(0, 0, 1); - - verifyDetailsRowHeight(5, detailsDefaultHeight, 1); - verifyDetailsDecoratorLocation(5, 1, 0); - } - - @Test public void testDetailsHeightWithGenerator() { openTestURL(); useGenerator(true); diff --git a/uitest/src/com/vaadin/tests/components/grid/GridEditorFrozenColumnsUI.java b/uitest/src/com/vaadin/tests/components/grid/GridEditorFrozenColumnsUI.java new file mode 100644 index 0000000000..d2414a8c40 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridEditorFrozenColumnsUI.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import com.vaadin.tests.util.PersonContainer; +import com.vaadin.ui.Grid; + +public class GridEditorFrozenColumnsUI extends GridEditorUI { + + @Override + protected Grid createGrid(PersonContainer container) { + Grid grid = super.createGrid(container); + + grid.setFrozenColumnCount(2); + + grid.setWidth("600px"); + + return grid; + } + + @Override + protected Integer getTicketNumber() { + return 16727; + } + + @Override + protected String getTestDescription() { + return "Frozen columns should also freeze cells in editor."; + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridEditorFrozenColumnsUITest.java b/uitest/src/com/vaadin/tests/components/grid/GridEditorFrozenColumnsUITest.java new file mode 100644 index 0000000000..75d71a3c40 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridEditorFrozenColumnsUITest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import java.io.IOException; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.elements.GridElement.GridCellElement; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.tb3.MultiBrowserTest; + +@TestCategory("grid") +public class GridEditorFrozenColumnsUITest extends MultiBrowserTest { + + @Test + public void testEditorWithFrozenColumns() throws IOException { + openTestURL(); + + openEditor(10); + + compareScreen("noscroll"); + + scrollGridHorizontallyTo(100); + + compareScreen("scrolled"); + } + + private void openEditor(int rowIndex) { + GridElement grid = $(GridElement.class).first(); + + GridCellElement cell = grid.getCell(rowIndex, 1); + + new Actions(driver).moveToElement(cell).doubleClick().build().perform(); + } + + private void scrollGridHorizontallyTo(double px) { + executeScript("arguments[0].scrollLeft = " + px, + getGridHorizontalScrollbar()); + } + + private Object executeScript(String script, WebElement element) { + final WebDriver driver = getDriver(); + if (driver instanceof JavascriptExecutor) { + final JavascriptExecutor je = (JavascriptExecutor) driver; + return je.executeScript(script, element); + } else { + throw new IllegalStateException("current driver " + + getDriver().getClass().getName() + " is not a " + + JavascriptExecutor.class.getSimpleName()); + } + } + + private WebElement getGridHorizontalScrollbar() { + return getDriver() + .findElement( + By.xpath("//div[contains(@class, \"v-grid-scroller-horizontal\")]")); + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridEditorUI.java b/uitest/src/com/vaadin/tests/components/grid/GridEditorUI.java index 60e241bae3..0a302967e8 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridEditorUI.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridEditorUI.java @@ -28,6 +28,10 @@ public class GridEditorUI extends AbstractTestUI { protected void setup(VaadinRequest request) { PersonContainer container = PersonContainer.createWithTestData(); + addComponent(createGrid(container)); + } + + protected Grid createGrid(PersonContainer container) { Grid grid = new Grid(container); // Don't use address since there's no converter @@ -43,7 +47,7 @@ public class GridEditorUI extends AbstractTestUI { grid.getColumn("phoneNumber").getEditorField().setReadOnly(true); - addComponent(grid); + return grid; } } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridEditorUITest.java b/uitest/src/com/vaadin/tests/components/grid/GridEditorUITest.java index 47dc90e33a..3d0b3bb071 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridEditorUITest.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridEditorUITest.java @@ -45,7 +45,7 @@ public class GridEditorUITest extends MultiBrowserTest { openEditor(10); - assertTrue("Edtor should be opened with a password field", + assertTrue("Editor should be opened with a password field", isElementPresent(PasswordFieldElement.class)); assertFalse("Notification was present", diff --git a/uitest/src/com/vaadin/tests/components/grid/GridFastAsyncUpdate.java b/uitest/src/com/vaadin/tests/components/grid/GridFastAsyncUpdate.java new file mode 100644 index 0000000000..31fe0275a5 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridFastAsyncUpdate.java @@ -0,0 +1,148 @@ +package com.vaadin.tests.components.grid; + +import java.util.Calendar; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; + +import com.vaadin.annotations.Push; +import com.vaadin.annotations.Theme; +import com.vaadin.data.Item; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.event.SelectionEvent; +import com.vaadin.event.SelectionEvent.SelectionListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.VerticalLayout; + +@Push +@Theme("valo") +@SuppressWarnings("serial") +public class GridFastAsyncUpdate extends AbstractTestUI { + + private final Runnable addRowsTask = new Runnable() { + @Override + public void run() { + System.out.println("Logging..."); + try { + Random random = new Random(); + while (!Thread.currentThread().isInterrupted()) { + Thread.sleep(random.nextInt(100)); + + GridFastAsyncUpdate.this.access(new Runnable() { + @SuppressWarnings("unchecked") + @Override + public void run() { + + ++counter; + Item item = container.addItem(counter); + item.getItemProperty("sequenceNumber").setValue( + String.valueOf(counter)); + item.getItemProperty("millis").setValue( + String.valueOf(Calendar.getInstance() + .getTimeInMillis() - loggingStart)); + item.getItemProperty("level").setValue( + Level.INFO.toString()); + item.getItemProperty("message").setValue("Message"); + if (grid != null && !scrollLock) { + grid.scrollToEnd(); + } + } + }); + } + } catch (InterruptedException e) { + System.out.println("logging thread interrupted"); + } + } + }; + + private int counter; + + private Grid grid; + private IndexedContainer container; + private long loggingStart; + private volatile boolean scrollLock = false; + + @Override + protected void setup(VaadinRequest vaadinRequest) { + final VerticalLayout layout = new VerticalLayout(); + layout.setSizeFull(); + layout.setMargin(true); + addComponent(layout); + + HorizontalLayout buttons = new HorizontalLayout(); + layout.addComponent(buttons); + + final ExecutorService logExecutor = Executors.newSingleThreadExecutor(); + + final Button logButton = new Button("Start logging"); + logButton.addClickListener(new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + if ("Start logging".equals(logButton.getCaption())) { + loggingStart = Calendar.getInstance().getTimeInMillis(); + logExecutor.submit(addRowsTask); + logButton.setCaption("Stop logging"); + } else { + System.out.println("Stop logging..."); + try { + logExecutor.shutdownNow(); + } catch (Exception e) { + e.printStackTrace(); + } + logButton.setCaption("Start logging"); + } + } + }); + buttons.addComponent(logButton); + + final Button scrollButton = new Button("Stop scrolling"); + scrollButton.addClickListener(new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + if (!scrollLock) { + System.out.println("Stop scrolling"); + scrollButton.setCaption("Start scrolling"); + scrollLock = true; + } else { + System.out.println("Start scrolling"); + scrollButton.setCaption("Stop scrolling"); + scrollLock = false; + } + } + }); + buttons.addComponent(scrollButton); + + container = new IndexedContainer(); + container.addContainerProperty("sequenceNumber", String.class, null); + container.addContainerProperty("millis", String.class, null); + container.addContainerProperty("level", String.class, null); + container.addContainerProperty("message", String.class, null); + + grid = new Grid(container); + grid.setWidth("100%"); + grid.setImmediate(true); + grid.setSelectionMode(SelectionMode.SINGLE); + grid.addSelectionListener(new SelectionListener() { + @Override + public void select(final SelectionEvent event) { + if (grid.getSelectedRow() != null) { + disableScroll(); + } + } + }); + + layout.addComponent(grid); + layout.setExpandRatio(grid, 1.0f); + } + + protected void disableScroll() { + scrollLock = true; + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/grid/GridScrollToLineWhileResizing.java b/uitest/src/com/vaadin/tests/components/grid/GridScrollToLineWhileResizing.java new file mode 100644 index 0000000000..194a9a3acc --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridScrollToLineWhileResizing.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import com.vaadin.data.Item; +import com.vaadin.data.util.IndexedContainer; +import com.vaadin.event.SelectionEvent; +import com.vaadin.event.SelectionEvent.SelectionListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.SelectionMode; +import com.vaadin.ui.VerticalSplitPanel; + +public class GridScrollToLineWhileResizing extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + + final VerticalSplitPanel vsp = new VerticalSplitPanel(); + vsp.setWidth(500, Unit.PIXELS); + vsp.setHeight(500, Unit.PIXELS); + vsp.setSplitPosition(100, Unit.PERCENTAGE); + addComponent(vsp); + + IndexedContainer indexedContainer = new IndexedContainer(); + indexedContainer.addContainerProperty("column1", String.class, ""); + + for (int i = 0; i < 100; i++) { + Item addItem = indexedContainer.addItem(i); + addItem.getItemProperty("column1").setValue("cell" + i); + } + + final Grid grid = new Grid(indexedContainer); + grid.setSizeFull(); + + grid.setSelectionMode(SelectionMode.SINGLE); + grid.addSelectionListener(new SelectionListener() { + + @Override + public void select(SelectionEvent event) { + vsp.setSplitPosition(50, Unit.PERCENTAGE); + grid.scrollTo(event.getSelected().iterator().next()); + } + }); + + vsp.setFirstComponent(grid); + } + + @Override + protected String getTestDescription() { + return "Tests scrollToLine while moving SplitPanel split position to resize the Grid on the same round-trip."; + } + + @Override + protected Integer getTicketNumber() { + return null; + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridScrollToLineWhileResizingTest.java b/uitest/src/com/vaadin/tests/components/grid/GridScrollToLineWhileResizingTest.java new file mode 100644 index 0000000000..aee1db7a85 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridScrollToLineWhileResizingTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.testbench.parallel.TestCategory; +import com.vaadin.tests.tb3.MultiBrowserTest; + +@TestCategory("grid") +public class GridScrollToLineWhileResizingTest extends MultiBrowserTest { + + @Test + public void testScrollToLineWorksWhileMovingSplitProgrammatically() { + openTestURL(); + + $(GridElement.class).first().getCell(21, 0).click(); + + List<WebElement> cells = findElements(By.className("v-grid-cell")); + boolean foundCell21 = false; + for (WebElement cell : cells) { + if ("cell21".equals(cell.getText())) { + foundCell21 = true; + } + } + + assertTrue(foundCell21); + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/grid/GridWithBrokenRenderer.java b/uitest/src/com/vaadin/tests/components/grid/GridWithBrokenRenderer.java new file mode 100644 index 0000000000..4d44eeb248 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridWithBrokenRenderer.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import com.vaadin.server.ClassResource; +import com.vaadin.server.Resource; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.tests.integration.FlagSeResource; +import com.vaadin.ui.Grid; +import com.vaadin.ui.renderers.ImageRenderer; + +public class GridWithBrokenRenderer extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + final Grid grid = new Grid(); + grid.addColumn("short", String.class); + grid.addColumn("icon", Resource.class); + grid.addColumn("country", String.class); + + grid.getColumn("icon").setRenderer(new ImageRenderer()); + addComponent(grid); + + grid.addRow("FI", new ClassResource("fi.gif"), "Finland"); + grid.addRow("SE", new FlagSeResource(), "Sweden"); + + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/GridWithBrokenRendererTest.java b/uitest/src/com/vaadin/tests/components/grid/GridWithBrokenRendererTest.java new file mode 100644 index 0000000000..011c8c92ce --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/GridWithBrokenRendererTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.testbench.elements.GridElement; +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class GridWithBrokenRendererTest extends SingleBrowserTest { + + @Test + public void ensureRendered() { + openTestURL(); + GridElement grid = $(GridElement.class).first(); + assertRow(grid, 0, "FI", "", "Finland"); + assertRow(grid, 1, "SE", "", "Sweden"); + } + + private void assertRow(GridElement grid, int row, String... texts) { + for (int column = 0; column < texts.length; column++) { + Assert.assertEquals("Cell " + row + "," + column, texts[column], + grid.getCell(row, column).getText()); + } + + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java b/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java index b38178b156..2e86053ef3 100644 --- a/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/JavaScriptRenderersTest.java @@ -43,6 +43,6 @@ public class JavaScriptRenderersTest extends MultiBrowserTest { // Verify onbrowserevent cell_1_1.click(); Assert.assertTrue(cell_1_1.getText().startsWith( - "Clicked 1 with key 1 at")); + "Clicked 1 with key 2 at")); } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java index ef51cdf446..479ece71ca 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java @@ -29,6 +29,8 @@ import java.util.Random; import com.vaadin.data.Container.Filter; import com.vaadin.data.Item; import com.vaadin.data.Property; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.fieldgroup.FieldGroup.CommitException; import com.vaadin.data.sort.Sort; import com.vaadin.data.sort.SortOrder; @@ -48,7 +50,9 @@ import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Component; import com.vaadin.ui.CssLayout; +import com.vaadin.ui.Field; import com.vaadin.ui.Grid; +import com.vaadin.ui.Grid.CellDescriptionGenerator; import com.vaadin.ui.Grid.CellReference; import com.vaadin.ui.Grid.CellStyleGenerator; import com.vaadin.ui.Grid.Column; @@ -57,10 +61,15 @@ import com.vaadin.ui.Grid.ColumnReorderListener; import com.vaadin.ui.Grid.ColumnVisibilityChangeEvent; import com.vaadin.ui.Grid.ColumnVisibilityChangeListener; import com.vaadin.ui.Grid.DetailsGenerator; +import com.vaadin.ui.Grid.EditorCloseEvent; +import com.vaadin.ui.Grid.EditorListener; +import com.vaadin.ui.Grid.EditorMoveEvent; +import com.vaadin.ui.Grid.EditorOpenEvent; import com.vaadin.ui.Grid.FooterCell; import com.vaadin.ui.Grid.HeaderCell; import com.vaadin.ui.Grid.HeaderRow; import com.vaadin.ui.Grid.MultiSelectionModel; +import com.vaadin.ui.Grid.RowDescriptionGenerator; import com.vaadin.ui.Grid.RowReference; import com.vaadin.ui.Grid.RowStyleGenerator; import com.vaadin.ui.Grid.SelectionMode; @@ -123,6 +132,45 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { } }; + private RowDescriptionGenerator rowDescriptionGenerator = new RowDescriptionGenerator() { + + @Override + public String getDescription(RowReference row) { + return "Row tooltip for row " + row.getItemId(); + } + }; + + private CellDescriptionGenerator cellDescriptionGenerator = new CellDescriptionGenerator() { + + @Override + public String getDescription(CellReference cell) { + if ("Column 0".equals(cell.getPropertyId())) { + return "Cell tooltip for row " + cell.getItemId() + + ", column 0"; + } else { + return null; + } + } + }; + + private ItemClickListener editorOpeningItemClickListener = new ItemClickListener() { + + @Override + public void itemClick(ItemClickEvent event) { + grid.editItem(event.getItemId()); + } + }; + + private ValueChangeListener reactiveValueChanger = new ValueChangeListener() { + @Override + @SuppressWarnings("unchecked") + public void valueChange(ValueChangeEvent event) { + Object id = grid.getEditedItemId(); + grid.getContainerDataSource().getContainerProperty(id, "Column 2") + .setValue("Modified"); + } + }; + private ColumnReorderListener columnReorderListener = new ColumnReorderListener() { @Override @@ -273,6 +321,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { new NumberRenderer(new DecimalFormat("0,000.00", DecimalFormatSymbols.getInstance(new Locale("fi", "FI"))))); + grid.getColumn(getColumnProperty(col++)).setRenderer( new DateRenderer(new SimpleDateFormat("dd.MM.yy HH:mm"))); grid.getColumn(getColumnProperty(col++)).setRenderer( @@ -362,49 +411,58 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { } private void addFilterActions() { - createClickAction("Column 1 starts with \"(23\"", "Filter", - new Command<Grid, Void>() { - @Override - public void execute(Grid grid, Void value, Object data) { - ds.addContainerFilter(new Filter() { + createBooleanAction("Column 1 starts with \"(23\"", "Filter", false, + new Command<Grid, Boolean>() { + Filter filter = new Filter() { + @Override + public boolean passesFilter(Object itemId, Item item) { + return item.getItemProperty("Column 1").getValue() + .toString().startsWith("(23"); + } - @Override - public boolean passesFilter(Object itemId, Item item) - throws UnsupportedOperationException { - return item.getItemProperty("Column 1") - .getValue().toString() - .startsWith("(23"); - } + @Override + public boolean appliesToProperty(Object propertyId) { + return propertyId.equals("Column 1"); + } + }; - @Override - public boolean appliesToProperty(Object propertyId) { - return propertyId.equals("Column 1"); - } - }); + @Override + public void execute(Grid grid, Boolean value, Object data) { + if (value) { + ds.addContainerFilter(filter); + } else { + ds.removeContainerFilter(filter); + } } - }, null); + }); - createClickAction("Add impassable filter", "Filter", - new Command<Grid, Void>() { - @Override - public void execute(Grid c, Void value, Object data) { - ds.addContainerFilter(new Filter() { - @Override - public boolean passesFilter(Object itemId, Item item) - throws UnsupportedOperationException { - return false; - } + createBooleanAction("Impassable filter", "Filter", false, + new Command<Grid, Boolean>() { + Filter filter = new Filter() { + @Override + public boolean passesFilter(Object itemId, Item item) { + return false; + } - @Override - public boolean appliesToProperty(Object propertyId) { - return true; - } - }); + @Override + public boolean appliesToProperty(Object propertyId) { + return true; + } + }; + + @Override + public void execute(Grid c, Boolean value, Object data) { + if (value) { + ds.addContainerFilter(filter); + } else { + ds.removeContainerFilter(filter); + } } - }, null); + }); } protected void createGridActions() { + LinkedHashMap<String, String> primaryStyleNames = new LinkedHashMap<String, String>(); primaryStyleNames.put("v-grid", "v-grid"); primaryStyleNames.put("v-escalator", "v-escalator"); @@ -594,6 +652,25 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { } }); + createBooleanAction("Row description generator", "State", false, + new Command<Grid, Boolean>() { + + @Override + public void execute(Grid c, Boolean value, Object data) { + c.setRowDescriptionGenerator(value ? rowDescriptionGenerator + : null); + } + }); + + createBooleanAction("Cell description generator", "State", false, + new Command<Grid, Boolean>() { + @Override + public void execute(Grid c, Boolean value, Object data) { + c.setCellDescriptionGenerator(value ? cellDescriptionGenerator + : null); + } + }); + LinkedHashMap<String, Integer> frozenOptions = new LinkedHashMap<String, Integer>(); for (int i = -1; i <= COLUMNS; i++) { frozenOptions.put(String.valueOf(i), Integer.valueOf(i)); @@ -638,6 +715,39 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { } }); + + createBooleanAction("EditorOpeningItemClickListener", "State", false, + new Command<Grid, Boolean>() { + + @Override + public void execute(Grid c, Boolean value, Object data) { + if (!value) { + c.removeItemClickListener(editorOpeningItemClickListener); + } else { + c.addItemClickListener(editorOpeningItemClickListener); + } + } + + }); + createBooleanAction("ReactiveValueChanger", "State", false, + new Command<Grid, Boolean>() { + + @Override + public void execute(Grid c, Boolean value, Object data) { + Field<?> targetField = grid.getEditorFieldGroup() + .getField("Column 0"); + if (targetField != null) { + if (!value) { + targetField + .removeValueChangeListener(reactiveValueChanger); + } else { + targetField + .addValueChangeListener(reactiveValueChanger); + } + } + } + + }); createBooleanAction("ColumnReorderListener", "State", false, new Command<Grid, Boolean>() { @@ -661,7 +771,6 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { } } }); - createBooleanAction("Single select allow deselect", "State", singleSelectAllowDeselect, new Command<Grid, Boolean>() { @Override @@ -683,6 +792,26 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { c.setColumnReorderingAllowed(value); } }); + + createClickAction("Select all", "State", new Command<Grid, String>() { + @Override + public void execute(Grid c, String value, Object data) { + SelectionModel selectionModel = c.getSelectionModel(); + if (selectionModel instanceof SelectionModel.Multi) { + ((SelectionModel.Multi) selectionModel).selectAll(); + } + } + }, null); + + createClickAction("Select none", "State", new Command<Grid, String>() { + @Override + public void execute(Grid c, String value, Object data) { + SelectionModel selectionModel = c.getSelectionModel(); + if (selectionModel instanceof SelectionModel.Multi) { + ((SelectionModel.Multi) selectionModel).deselectAll(); + } + } + }, null); } protected void createHeaderActions() { @@ -1045,6 +1174,18 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { } }); + + createClickAction("All columns expanding, Col 0 has max width of 30px", + "Columns", new Command<Grid, Boolean>() { + + @Override + public void execute(Grid c, Boolean value, Object data) { + for (Column col : grid.getColumns()) { + col.setWidthUndefined(); + } + grid.getColumns().get(0).setMaximumWidth(30); + } + }, null); } private static String getColumnProperty(int c) { @@ -1218,6 +1359,14 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { } }); + createBooleanAction("Buffered mode", "Editor", true, + new Command<Grid, Boolean>() { + @Override + public void execute(Grid c, Boolean value, Object data) { + c.setEditorBuffered(value); + } + }); + createClickAction("Edit item 5", "Editor", new Command<Grid, String>() { @Override public void execute(Grid c, String value, Object data) { @@ -1265,6 +1414,30 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> { c.setEditorCancelCaption("ʃǝɔuɐↃ"); } }, null); + + createClickAction("Add editor state listener", "Editor", + new Command<Grid, String>() { + @Override + public void execute(Grid grid, String value, Object data) { + grid.addEditorListener(new EditorListener() { + @Override + public void editorOpened(EditorOpenEvent e) { + log("Editor opened"); + } + + @Override + public void editorMoved(EditorMoveEvent e) { + log("Editor moved"); + } + + @Override + public void editorClosed(EditorCloseEvent e) { + log("Editor closed"); + } + }); + } + }, null); + } @SuppressWarnings("boxing") diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java index a307aaca16..a169e701c0 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridColumnHidingTest.java @@ -823,6 +823,7 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { @Test public void testColumnHiding_detailsRowIsOpen_renderedCorrectly() { + selectMenuPath("Component", "Row details", "Set generator"); selectMenuPath("Component", "Row details", "Toggle details for...", "Row 1"); assertColumnHeaderOrder(0, 1, 2, 3, 4); @@ -870,7 +871,10 @@ public class GridColumnHidingTest extends GridBasicClientFeaturesTest { selectMenuPath("Component", "Columns", "Column 0", "Hidable"); getSidebarOpenButton().click(); verifySidebarOpened(); - findElement(By.className("v-app")).click(); + // Click somewhere far from Grid. + new Actions(getDriver()) + .moveToElement(findElement(By.className("v-app")), 600, 600) + .click().perform(); verifySidebarClosed(); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java new file mode 100644 index 0000000000..ed712361a6 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.openqa.selenium.By; + +public class GridDescriptionGeneratorTest extends GridBasicFeaturesTest { + + @Test + public void testCellDescription() { + openTestURL(); + selectMenuPath("Component", "State", "Cell description generator"); + + getGridElement().getCell(1, 0).showTooltip(); + String tooltipText = findElement(By.className("v-tooltip-text")) + .getText(); + assertEquals("Tooltip text", "Cell tooltip for row 1, column 0", + tooltipText); + + getGridElement().getCell(1, 1).showTooltip(); + assertTrue("Tooltip should not be present in cell (1, 1) ", + findElement(By.className("v-tooltip-text")).getText().isEmpty()); + } + + @Test + public void testRowDescription() { + openTestURL(); + selectMenuPath("Component", "State", "Row description generator"); + + getGridElement().getCell(5, 3).showTooltip(); + String tooltipText = findElement(By.className("v-tooltip-text")) + .getText(); + assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText); + + getGridElement().getCell(15, 3).showTooltip(); + tooltipText = findElement(By.className("v-tooltip-text")).getText(); + assertEquals("Tooltip text", "Row tooltip for row 15", tooltipText); + } + + @Test + public void testRowAndCellDescription() { + openTestURL(); + selectMenuPath("Component", "State", "Row description generator"); + selectMenuPath("Component", "State", "Cell description generator"); + + getGridElement().getCell(5, 0).showTooltip(); + String tooltipText = findElement(By.className("v-tooltip-text")) + .getText(); + assertEquals("Tooltip text", "Cell tooltip for row 5, column 0", + tooltipText); + + getGridElement().getCell(5, 3).showTooltip(); + tooltipText = findElement(By.className("v-tooltip-text")).getText(); + assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java index 88158c7f6f..01e7e52923 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridDetailsClientTest.java @@ -35,8 +35,8 @@ import com.vaadin.shared.ui.grid.ScrollDestination; import com.vaadin.testbench.By; import com.vaadin.testbench.ElementQuery; import com.vaadin.testbench.TestBenchElement; -import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.components.grid.basicfeatures.GridBasicClientFeaturesTest; +import com.vaadin.tests.tb3.newelements.FixedNotificationElement; public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @@ -59,14 +59,10 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { getGridElement().getDetails(1)); } - @Test - public void nullRendererShowsDetailsPlaceholder() { + @Test(expected = NoSuchElementException.class) + public void nullRendererDoesNotShowDetailsPlaceholder() { toggleDetailsFor(1); - TestBenchElement details = getGridElement().getDetails(1); - assertNotNull("details for row 1 should not exist at the start", - details); - assertTrue("details should've been empty for null renderer", details - .getText().isEmpty()); + getGridElement().getDetails(1); } @Test @@ -79,14 +75,12 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { details.getText().startsWith("Row: 1.")); } - @Test - public void openDetailsThenAppyRenderer() { + @Test(expected = NoSuchElementException.class) + public void openDetailsThenAppyRendererShouldNotShowDetails() { toggleDetailsFor(1); selectMenuPath(SET_GENERATOR); - TestBenchElement details = getGridElement().getDetails(1); - assertTrue("Unexpected details content", - details.getText().startsWith("Row: 1.")); + getGridElement().getDetails(1); } @Test @@ -112,12 +106,12 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void errorUpdaterShowsErrorNotification() { assertFalse("No notifications should've been at the start", - $(NotificationElement.class).exists()); + $(FixedNotificationElement.class).exists()); - toggleDetailsFor(1); selectMenuPath(SET_FAULTY_GENERATOR); + toggleDetailsFor(1); - ElementQuery<NotificationElement> notification = $(NotificationElement.class); + ElementQuery<FixedNotificationElement> notification = $(FixedNotificationElement.class); assertTrue("Was expecting an error notification here", notification.exists()); notification.first().close(); @@ -126,13 +120,25 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { getGridElement().getDetails(1).getText()); } - @Test - public void updaterStillWorksAfterError() { + @Test(expected = NoSuchElementException.class) + public void detailsClosedWhenResettingGenerator() { + + selectMenuPath(SET_GENERATOR); toggleDetailsFor(1); selectMenuPath(SET_FAULTY_GENERATOR); - $(NotificationElement.class).first().close(); + getGridElement().getDetails(1); + } + + @Test + public void settingNewGeneratorStillWorksAfterError() { + selectMenuPath(SET_FAULTY_GENERATOR); + toggleDetailsFor(1); + $(FixedNotificationElement.class).first().close(); + toggleDetailsFor(1); + selectMenuPath(SET_GENERATOR); + toggleDetailsFor(1); assertNotEquals( "New details should've been generated even after error", "", @@ -184,6 +190,7 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void rowElementClassNames() { + selectMenuPath(SET_GENERATOR); toggleDetailsFor(0); toggleDetailsFor(1); @@ -196,25 +203,28 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void scrollDownToRowWithDetails() { + selectMenuPath(SET_GENERATOR); toggleDetailsFor(100); scrollToRow(100, ScrollDestination.ANY); - Range validScrollRange = Range.between(1700, 1715); + Range validScrollRange = Range.between(1691, 1706); assertTrue(validScrollRange.contains(getGridVerticalScrollPos())); } @Test public void scrollUpToRowWithDetails() { + selectMenuPath(SET_GENERATOR); toggleDetailsFor(100); scrollGridVerticallyTo(999999); scrollToRow(100, ScrollDestination.ANY); - Range validScrollRange = Range.between(1990, 2010); + Range validScrollRange = Range.between(1981, 2001); assertTrue(validScrollRange.contains(getGridVerticalScrollPos())); } @Test public void cannotScrollBeforeTop() { + selectMenuPath(SET_GENERATOR); toggleDetailsFor(1); scrollToRow(0, ScrollDestination.END); assertEquals(0, getGridVerticalScrollPos()); @@ -222,10 +232,11 @@ public class GridDetailsClientTest extends GridBasicClientFeaturesTest { @Test public void cannotScrollAfterBottom() { + selectMenuPath(SET_GENERATOR); toggleDetailsFor(999); scrollToRow(999, ScrollDestination.START); - Range expectedRange = Range.withLength(19680, 20); + Range expectedRange = Range.withLength(19671, 20); assertTrue(expectedRange.contains(getGridVerticalScrollPos())); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java index f437589a39..0dd137db48 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/client/GridEditorClientTest.java @@ -81,9 +81,19 @@ public class GridEditorClientTest extends GridBasicClientFeaturesTest { getGridElement().getCell(4, 0).doubleClick(); assertNotNull(getEditor()); - getCancelButton().click(); + // Move focus to the third input field + getEditor().findElements(By.className("gwt-TextBox")).get(2).click(); + + // Press save button + getSaveButton().click(); + + // Make sure the editor went away assertNull(getEditor()); + // Check that focus has moved to cell 4,2 - the last one that was + // focused in Editor + assertTrue(getGridElement().getCell(4, 2).isFocused()); + // Disable editor selectMenuPath("Component", "Editor", "Enabled"); @@ -134,14 +144,16 @@ public class GridEditorClientTest extends GridBasicClientFeaturesTest { @Test public void testWithSelectionColumn() throws Exception { selectMenuPath("Component", "State", "Selection mode", "multi"); + selectMenuPath("Component", "State", "Frozen column count", + "-1 columns"); selectMenuPath(EDIT_ROW_5); - WebElement editorCells = findElement(By - .className("v-grid-editor-cells")); + WebElement editorCells = findElements( + By.className("v-grid-editor-cells")).get(1); List<WebElement> selectorDivs = editorCells.findElements(By .cssSelector("div")); - assertTrue("selector column cell should've been empty", selectorDivs + assertFalse("selector column cell should've had contents", selectorDivs .get(0).getAttribute("innerHTML").isEmpty()); assertFalse("normal column cell shoul've had contents", selectorDivs .get(1).getAttribute("innerHTML").isEmpty()); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnMaxWidthTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnMaxWidthTest.java new file mode 100644 index 0000000000..7f19559aec --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridColumnMaxWidthTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures.server; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; + +public class GridColumnMaxWidthTest extends GridBasicFeaturesTest { + + @Test + public void testMaxWidthAffectsColumnWidth() { + setDebug(true); + openTestURL(); + + selectMenuPath("Component", "Columns", + "All columns expanding, Col 0 has max width of 30px"); + + assertEquals("Column 0 did not obey max width of 30px.", 30, + getGridElement().getCell(0, 0).getSize().getWidth()); + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java index 4ea64073f3..a9ab7027db 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridDetailsServerTest.java @@ -22,7 +22,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; @@ -59,7 +58,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { openTestURL(); } - @Test + @Test(expected = NoSuchElementException.class) public void openVisibleDetails() { try { getGridElement().getDetails(0); @@ -68,8 +67,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { // expected } selectMenuPath(OPEN_FIRST_ITEM_DETAILS); - assertNotNull("details should've opened", getGridElement() - .getDetails(0)); + getGridElement().getDetails(0); } @Test(expected = NoSuchElementException.class) @@ -99,6 +97,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { @Test public void openDetailsOutsideOfActiveRange() throws InterruptedException { getGridElement().scroll(10000); + selectMenuPath(DETAILS_GENERATOR_WATCHING); selectMenuPath(OPEN_FIRST_ITEM_DETAILS); getGridElement().scroll(0); Thread.sleep(50); @@ -196,14 +195,11 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { assertEquals("Two", getGridElement().getDetails(0).getText()); } - @Ignore("This use case is not currently supported by Grid. If the detail " - + "is out of view, the component is detached from the UI and a " - + "new instance is generated when scrolled back. Support will " - + "maybe be incorporated at a later time") @Test public void hierarchyChangesWorkInDetailsWhileOutOfView() { selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); selectMenuPath(OPEN_FIRST_ITEM_DETAILS); + assertEquals("One", getGridElement().getDetails(0).getText()); scrollGridVerticallyTo(10000); selectMenuPath(CHANGE_HIERARCHY); scrollGridVerticallyTo(0); @@ -219,13 +215,15 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { @Test public void swappingDetailsGenerators_shownDetails() { + selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); selectMenuPath(OPEN_FIRST_ITEM_DETAILS); - assertTrue("Details should be empty at the start", getGridElement() - .getDetails(0).getText().isEmpty()); + assertTrue("Details should contain 'One' at first", getGridElement() + .getDetails(0).getText().contains("One")); selectMenuPath(DETAILS_GENERATOR_WATCHING); - assertFalse("Details should not be empty after swapping generator", - getGridElement().getDetails(0).getText().isEmpty()); + assertFalse( + "Details should contain 'Watching' after swapping generator", + getGridElement().getDetails(0).getText().contains("Watching")); } @Test @@ -239,6 +237,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { @Test public void swappingDetailsGenerators_whileDetailsScrolledOut_showAfter() { scrollGridVerticallyTo(1000); + selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); selectMenuPath(OPEN_FIRST_ITEM_DETAILS); selectMenuPath(DETAILS_GENERATOR_WATCHING); scrollGridVerticallyTo(0); @@ -250,6 +249,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { @Test public void swappingDetailsGenerators_whileDetailsScrolledOut_showBefore() { + selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); selectMenuPath(OPEN_FIRST_ITEM_DETAILS); selectMenuPath(DETAILS_GENERATOR_WATCHING); scrollGridVerticallyTo(1000); @@ -261,6 +261,7 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { @Test public void swappingDetailsGenerators_whileDetailsScrolledOut_showBeforeAndAfter() { + selectMenuPath(DETAILS_GENERATOR_HIERARCHICAL); selectMenuPath(OPEN_FIRST_ITEM_DETAILS); selectMenuPath(DETAILS_GENERATOR_WATCHING); scrollGridVerticallyTo(1000); @@ -272,24 +273,6 @@ public class GridDetailsServerTest extends GridBasicFeaturesTest { } @Test - public void nullDetailComponentToggling() { - selectMenuPath(OPEN_FIRST_ITEM_DETAILS); - selectMenuPath(DETAILS_GENERATOR_WATCHING); - selectMenuPath(DETAILS_GENERATOR_NULL); - - try { - assertTrue("Details should be empty with null component", - getGridElement().getDetails(0).getText().isEmpty()); - } catch (NoSuchElementException e) { - fail("Expected to find a details row with empty content"); - } - - selectMenuPath(DETAILS_GENERATOR_WATCHING); - assertFalse("Details should be not empty with details component", - getGridElement().getDetails(0).getText().isEmpty()); - } - - @Test public void noAssertErrorsOnEmptyDetailsAndScrollDown() { selectMenuPath(OPEN_FIRST_ITEM_DETAILS); scrollGridVerticallyTo(500); diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorBufferedTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorBufferedTest.java new file mode 100644 index 0000000000..57f4b877df --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorBufferedTest.java @@ -0,0 +1,264 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.shared.ui.grid.GridConstants; +import com.vaadin.testbench.elements.GridElement.GridCellElement; +import com.vaadin.testbench.elements.GridElement.GridEditorElement; +import com.vaadin.testbench.elements.NotificationElement; + +public class GridEditorBufferedTest extends GridEditorTest { + + @Override + @Before + public void setUp() { + super.setUp(); + } + + @Test + public void testSave() { + selectMenuPath(EDIT_ITEM_100); + + WebElement textField = getEditorWidgets().get(0); + + textField.click(); + + textField.sendKeys(" changed"); + + WebElement saveButton = getEditor().findElement( + By.className("v-grid-editor-save")); + + saveButton.click(); + + assertEquals("(100, 0) changed", getGridElement().getCell(100, 0) + .getText()); + } + + @Test + public void testProgrammaticSave() { + selectMenuPath(EDIT_ITEM_100); + + WebElement textField = getEditorWidgets().get(0); + + textField.click(); + + textField.sendKeys(" changed"); + + selectMenuPath("Component", "Editor", "Save"); + + assertEquals("(100, 0) changed", getGridElement().getCell(100, 0) + .getText()); + } + + @Test + public void testInvalidEdition() { + selectMenuPath(EDIT_ITEM_5); + assertFalse(logContainsText("Exception occured, java.lang.IllegalStateException")); + + GridEditorElement editor = getGridElement().getEditor(); + + assertFalse( + "Field 7 should not have been marked with an error before error", + editor.isFieldErrorMarked(7)); + + WebElement intField = editor.getField(7); + intField.clear(); + intField.sendKeys("banana phone"); + editor.save(); + + assertEquals("Column 7: Could not convert value to Integer", + editor.getErrorMessage()); + assertTrue("Field 7 should have been marked with an error after error", + editor.isFieldErrorMarked(7)); + editor.cancel(); + + selectMenuPath(EDIT_ITEM_100); + assertFalse("Exception should not exist", + isElementPresent(NotificationElement.class)); + assertEquals("There should be no editor error message", null, + getGridElement().getEditor().getErrorMessage()); + } + + @Test + public void testEditorInDisabledGrid() { + int originalScrollPos = getGridVerticalScrollPos(); + + selectMenuPath(EDIT_ITEM_5); + assertEditorOpen(); + + selectMenuPath("Component", "State", "Enabled"); + assertEditorOpen(); + + GridEditorElement editor = getGridElement().getEditor(); + editor.save(); + assertEditorOpen(); + + editor.cancel(); + assertEditorOpen(); + + selectMenuPath("Component", "State", "Enabled"); + + scrollGridVerticallyTo(100); + assertEquals( + "Grid shouldn't scroll vertically while editing in buffered mode", + originalScrollPos, getGridVerticalScrollPos()); + } + + @Test + public void testCaptionChange() { + selectMenuPath(EDIT_ITEM_5); + assertEquals("Save button caption should've been \"" + + GridConstants.DEFAULT_SAVE_CAPTION + "\" to begin with", + GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText()); + assertEquals("Cancel button caption should've been \"" + + GridConstants.DEFAULT_CANCEL_CAPTION + "\" to begin with", + GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton() + .getText()); + + selectMenuPath("Component", "Editor", "Change save caption"); + assertNotEquals( + "Save button caption should've changed while editor is open", + GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText()); + + getCancelButton().click(); + + selectMenuPath("Component", "Editor", "Change cancel caption"); + selectMenuPath(EDIT_ITEM_5); + assertNotEquals( + "Cancel button caption should've changed while editor is closed", + GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton() + .getText()); + } + + @Test(expected = NoSuchElementException.class) + public void testVerticalScrollLocking() { + selectMenuPath(EDIT_ITEM_5); + getGridElement().getCell(200, 0); + } + + @Test + public void testScrollDisabledOnProgrammaticOpen() { + int originalScrollPos = getGridVerticalScrollPos(); + + selectMenuPath(EDIT_ITEM_5); + + scrollGridVerticallyTo(100); + assertEquals( + "Grid shouldn't scroll vertically while editing in buffered mode", + originalScrollPos, getGridVerticalScrollPos()); + } + + @Test + public void testScrollDisabledOnMouseOpen() { + int originalScrollPos = getGridVerticalScrollPos(); + + GridCellElement cell_5_0 = getGridElement().getCell(5, 0); + new Actions(getDriver()).doubleClick(cell_5_0).perform(); + + scrollGridVerticallyTo(100); + assertEquals( + "Grid shouldn't scroll vertically while editing in buffered mode", + originalScrollPos, getGridVerticalScrollPos()); + } + + @Test + public void testScrollDisabledOnKeyboardOpen() { + int originalScrollPos = getGridVerticalScrollPos(); + + GridCellElement cell_5_0 = getGridElement().getCell(5, 0); + cell_5_0.click(); + new Actions(getDriver()).sendKeys(Keys.ENTER).perform(); + + scrollGridVerticallyTo(100); + assertEquals( + "Grid shouldn't scroll vertically while editing in buffered mode", + originalScrollPos, getGridVerticalScrollPos()); + } + + @Test + public void testMouseOpeningClosing() { + + getGridElement().getCell(4, 0).doubleClick(); + assertEditorOpen(); + + getCancelButton().click(); + assertEditorClosed(); + + selectMenuPath(TOGGLE_EDIT_ENABLED); + getGridElement().getCell(4, 0).doubleClick(); + assertEditorClosed(); + } + + @Test + public void testMouseOpeningDisabledWhenOpen() { + selectMenuPath(EDIT_ITEM_5); + + getGridElement().getCell(4, 0).doubleClick(); + + assertEquals("Editor should still edit row 5", "(5, 0)", + getEditorWidgets().get(0).getAttribute("value")); + } + + @Test + public void testKeyboardOpeningDisabledWhenOpen() { + selectMenuPath(EDIT_ITEM_5); + + new Actions(getDriver()).click(getGridElement().getCell(4, 0)) + .sendKeys(Keys.ENTER).perform(); + + assertEquals("Editor should still edit row 5", "(5, 0)", + getEditorWidgets().get(0).getAttribute("value")); + } + + @Test + public void testProgrammaticOpeningDisabledWhenOpen() { + selectMenuPath(EDIT_ITEM_5); + assertEditorOpen(); + assertEquals("Editor should edit row 5", "(5, 0)", getEditorWidgets() + .get(0).getAttribute("value")); + + selectMenuPath(EDIT_ITEM_100); + boolean thrown = logContainsText("Exception occured, java.lang.IllegalStateException"); + assertTrue("IllegalStateException thrown", thrown); + + assertEditorOpen(); + assertEquals("Editor should still edit row 5", "(5, 0)", + getEditorWidgets().get(0).getAttribute("value")); + } + + @Test + public void testUserSortDisabledWhenOpen() { + selectMenuPath(EDIT_ITEM_5); + + getGridElement().getHeaderCell(0, 0).click(); + + assertEditorOpen(); + assertEquals("(2, 0)", getGridElement().getCell(2, 0).getText()); + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java index 0c39b3e509..0cba2ce34b 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorTest.java @@ -17,7 +17,6 @@ package com.vaadin.tests.components.grid.basicfeatures.server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -28,24 +27,26 @@ import org.junit.Before; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.Keys; -import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; -import com.vaadin.shared.ui.grid.GridConstants; +import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.GridElement.GridCellElement; import com.vaadin.testbench.elements.GridElement.GridEditorElement; -import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeatures; import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; -public class GridEditorTest extends GridBasicFeaturesTest { +public abstract class GridEditorTest extends GridBasicFeaturesTest { - private static final String[] EDIT_ITEM_5 = new String[] { "Component", + protected static final By BY_EDITOR_CANCEL = By + .className("v-grid-editor-cancel"); + protected static final By BY_EDITOR_SAVE = By + .className("v-grid-editor-save"); + protected static final String[] EDIT_ITEM_5 = new String[] { "Component", "Editor", "Edit item 5" }; - private static final String[] EDIT_ITEM_100 = new String[] { "Component", + protected static final String[] EDIT_ITEM_100 = new String[] { "Component", "Editor", "Edit item 100" }; - private static final String[] TOGGLE_EDIT_ENABLED = new String[] { + protected static final String[] TOGGLE_EDIT_ENABLED = new String[] { "Component", "Editor", "Enabled" }; @Before @@ -88,26 +89,6 @@ public class GridEditorTest extends GridBasicFeaturesTest { assertEditorOpen(); } - @Test(expected = NoSuchElementException.class) - public void testVerticalScrollLocking() { - selectMenuPath(EDIT_ITEM_5); - getGridElement().getCell(200, 0); - } - - @Test - public void testMouseOpeningClosing() { - - getGridElement().getCell(4, 0).doubleClick(); - assertEditorOpen(); - - getCancelButton().click(); - assertEditorClosed(); - - selectMenuPath(TOGGLE_EDIT_ENABLED); - getGridElement().getCell(4, 0).doubleClick(); - assertEditorClosed(); - } - @Test public void testKeyboardOpeningClosing() { @@ -141,237 +122,180 @@ public class GridEditorTest extends GridBasicFeaturesTest { assertEquals("<b>100</b>", widgets.get(8).getAttribute("value")); } - @Test - public void testSave() { - selectMenuPath(EDIT_ITEM_100); - - WebElement textField = getEditorWidgets().get(0); - - textField.click(); - - textField.sendKeys(" changed"); + protected void assertEditorOpen() { + assertEquals("Unexpected number of widgets", + GridBasicFeatures.EDITABLE_COLUMNS, getEditorWidgets().size()); + } - WebElement saveButton = getEditor().findElement( - By.className("v-grid-editor-save")); + protected void assertEditorClosed() { + assertNull("Editor is supposed to be closed", getEditor()); + } - saveButton.click(); + protected List<WebElement> getEditorWidgets() { + assertNotNull("Editor is supposed to be open", getEditor()); + return getEditor().findElements(By.className("v-textfield")); - assertEquals("(100, 0) changed", getGridElement().getCell(100, 0) - .getText()); } @Test - public void testProgrammaticSave() { - selectMenuPath(EDIT_ITEM_100); - - WebElement textField = getEditorWidgets().get(0); + public void testFocusOnMouseOpen() { - textField.click(); + GridCellElement cell = getGridElement().getCell(4, 2); - textField.sendKeys(" changed"); + cell.doubleClick(); - selectMenuPath("Component", "Editor", "Save"); + WebElement focused = getFocusedElement(); - assertEquals("(100, 0) changed", getGridElement().getCell(100, 0) - .getText()); + assertEquals("", "input", focused.getTagName()); + assertEquals("", cell.getText(), focused.getAttribute("value")); } @Test - public void testCaptionChange() { - selectMenuPath(EDIT_ITEM_5); - assertEquals("Save button caption should've been \"" - + GridConstants.DEFAULT_SAVE_CAPTION + "\" to begin with", - GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText()); - assertEquals("Cancel button caption should've been \"" - + GridConstants.DEFAULT_CANCEL_CAPTION + "\" to begin with", - GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton() - .getText()); - - selectMenuPath("Component", "Editor", "Change save caption"); - assertNotEquals( - "Save button caption should've changed while editor is open", - GridConstants.DEFAULT_SAVE_CAPTION, getSaveButton().getText()); - - getCancelButton().click(); - - selectMenuPath("Component", "Editor", "Change cancel caption"); - selectMenuPath(EDIT_ITEM_5); - assertNotEquals( - "Cancel button caption should've changed while editor is closed", - GridConstants.DEFAULT_CANCEL_CAPTION, getCancelButton() - .getText()); - } + public void testFocusOnKeyboardOpen() { - private void assertEditorOpen() { - assertNotNull("Editor is supposed to be open", getEditor()); - assertEquals("Unexpected number of widgets", - GridBasicFeatures.EDITABLE_COLUMNS, getEditorWidgets().size()); - } + GridCellElement cell = getGridElement().getCell(4, 2); - private void assertEditorClosed() { - assertNull("Editor is supposed to be closed", getEditor()); - } + cell.click(); + new Actions(getDriver()).sendKeys(Keys.ENTER).perform(); - private List<WebElement> getEditorWidgets() { - assertNotNull(getEditor()); - return getEditor().findElements(By.className("v-textfield")); + WebElement focused = getFocusedElement(); + assertEquals("", "input", focused.getTagName()); + assertEquals("", cell.getText(), focused.getAttribute("value")); } @Test - public void testInvalidEdition() { - selectMenuPath(EDIT_ITEM_5); - assertFalse(logContainsText("Exception occured, java.lang.IllegalStateException")); + public void testFocusOnProgrammaticOpenOnItemClick() { + selectMenuPath("Component", "State", "EditorOpeningItemClickListener"); - GridEditorElement editor = getGridElement().getEditor(); - - assertFalse( - "Field 7 should not have been marked with an error before error", - editor.isFieldErrorMarked(7)); + GridCellElement cell = getGridElement().getCell(4, 2); - WebElement intField = editor.getField(7); - intField.clear(); - intField.sendKeys("banana phone"); - editor.save(); + cell.click(); - assertEquals("Column 7: Could not convert value to Integer", - editor.getErrorMessage()); - assertTrue("Field 7 should have been marked with an error after error", - editor.isFieldErrorMarked(7)); - editor.cancel(); + WebElement focused = getFocusedElement(); - selectMenuPath(EDIT_ITEM_100); - assertFalse("Exception should not exist", - isElementPresent(NotificationElement.class)); - assertEquals("There should be no editor error message", null, - getGridElement().getEditor().getErrorMessage()); + assertEquals("", "input", focused.getTagName()); + assertEquals("", cell.getText(), focused.getAttribute("value")); } @Test - public void testNoScrollAfterProgrammaticOpen() { - int originalScrollPos = getGridVerticalScrollPos(); + public void testNoFocusOnProgrammaticOpen() { selectMenuPath(EDIT_ITEM_5); - scrollGridVerticallyTo(100); - assertEquals("Grid shouldn't scroll vertically while editing", - originalScrollPos, getGridVerticalScrollPos()); + WebElement focused = getFocusedElement(); + + assertEquals("Focus should remain in the menu", "menu", + focused.getAttribute("id")); } @Test - public void testNoScrollAfterMouseOpen() { - int originalScrollPos = getGridVerticalScrollPos(); + public void testUneditableColumn() { + selectMenuPath(EDIT_ITEM_5); + assertEditorOpen(); - GridCellElement cell_5_0 = getGridElement().getCell(5, 0); - new Actions(getDriver()).doubleClick(cell_5_0).perform(); + GridEditorElement editor = getGridElement().getEditor(); + assertFalse("Uneditable column should not have an editor widget", + editor.isEditable(3)); - scrollGridVerticallyTo(100); - assertEquals("Grid shouldn't scroll vertically while editing", - originalScrollPos, getGridVerticalScrollPos()); - } + String classNames = editor + .findElements(By.className("v-grid-editor-cells")).get(1) + .findElements(By.xpath("./div")).get(3).getAttribute("class"); - @Test - public void testNoScrollAfterKeyboardOpen() { - int originalScrollPos = getGridVerticalScrollPos(); + assertTrue("Noneditable cell should contain not-editable classname", + classNames.contains("not-editable")); - GridCellElement cell_5_0 = getGridElement().getCell(5, 0); - cell_5_0.click(); - new Actions(getDriver()).sendKeys(Keys.ENTER).perform(); + assertTrue("Noneditable cell should contain v-grid-cell classname", + classNames.contains("v-grid-cell")); - scrollGridVerticallyTo(100); - assertEquals("Grid shouldn't scroll vertically while editing", - originalScrollPos, getGridVerticalScrollPos()); + assertNoErrorNotifications(); } @Test - public void testEditorInDisabledGrid() { - int originalScrollPos = getGridVerticalScrollPos(); + public void testNoOpenFromHeaderOrFooter() { + selectMenuPath("Component", "Footer", "Visible"); - selectMenuPath(EDIT_ITEM_5); - assertEditorOpen(); + getGridElement().getHeaderCell(0, 0).doubleClick(); + assertEditorClosed(); - selectMenuPath("Component", "State", "Enabled"); - assertEditorOpen(); + new Actions(getDriver()).sendKeys(Keys.ENTER).perform(); + assertEditorClosed(); - GridEditorElement editor = getGridElement().getEditor(); - editor.save(); - assertEditorOpen(); + getGridElement().getFooterCell(0, 0).doubleClick(); + assertEditorClosed(); - editor.cancel(); - assertEditorOpen(); + new Actions(getDriver()).sendKeys(Keys.ENTER).perform(); + assertEditorClosed(); + } - selectMenuPath("Component", "State", "Enabled"); + public void testEditorMoveOnResize() { + selectMenuPath("Component", "Size", "Height", "500px"); + getGridElement().getCell(22, 0).doubleClick(); + assertEditorOpen(); - scrollGridVerticallyTo(100); - assertEquals("Grid shouldn't scroll vertically while editing", - originalScrollPos, getGridVerticalScrollPos()); - } + GridEditorElement editor = getGridElement().getEditor(); + TestBenchElement tableWrapper = getGridElement().getTableWrapper(); - @Test - public void testFocusOnMouseOpen() { + int tableWrapperBottom = tableWrapper.getLocation().getY() + + tableWrapper.getSize().getHeight(); + int editorBottom = editor.getLocation().getY() + + editor.getSize().getHeight(); - GridCellElement cell = getGridElement().getCell(4, 2); + assertTrue("Editor should not be initially outside grid", + tableWrapperBottom - editorBottom <= 2); - cell.doubleClick(); + selectMenuPath("Component", "Size", "Height", "300px"); + assertEditorOpen(); - WebElement focused = getFocusedElement(); + tableWrapperBottom = tableWrapper.getLocation().getY() + + tableWrapper.getSize().getHeight(); + editorBottom = editor.getLocation().getY() + + editor.getSize().getHeight(); - assertEquals("", "input", focused.getTagName()); - assertEquals("", cell.getText(), focused.getAttribute("value")); + assertTrue("Editor should not be outside grid after resize", + tableWrapperBottom - editorBottom <= 2); } - @Test - public void testFocusOnKeyboardOpen() { + public void testEditorDoesNotMoveOnResizeIfNotNeeded() { + selectMenuPath("Component", "Size", "Height", "500px"); - GridCellElement cell = getGridElement().getCell(4, 2); + selectMenuPath(EDIT_ITEM_5); + assertEditorOpen(); - cell.click(); - new Actions(getDriver()).sendKeys(Keys.ENTER).perform(); + GridEditorElement editor = getGridElement().getEditor(); - WebElement focused = getFocusedElement(); + int editorPos = editor.getLocation().getY(); - assertEquals("", "input", focused.getTagName()); - assertEquals("", cell.getText(), focused.getAttribute("value")); + selectMenuPath("Component", "Size", "Height", "300px"); + assertEditorOpen(); + + assertTrue("Editor should not have moved due to resize", + editorPos == editor.getLocation().getY()); } @Test - public void testNoFocusOnProgrammaticOpen() { - + public void testEditorClosedOnSort() { selectMenuPath(EDIT_ITEM_5); - WebElement focused = getFocusedElement(); - - assertEquals("Focus should remain in the menu", "menu", - focused.getAttribute("id")); - } + selectMenuPath("Component", "State", "Sort by column", "Column 0, ASC"); - @Override - protected WebElement getFocusedElement() { - return (WebElement) executeScript("return document.activeElement;"); + assertEditorClosed(); } @Test - public void testUneditableColumn() { + public void testEditorClosedOnFilter() { selectMenuPath(EDIT_ITEM_5); - assertEditorOpen(); - GridEditorElement editor = getGridElement().getEditor(); - assertFalse("Uneditable column should not have an editor widget", - editor.isEditable(3)); - assertEquals( - "Not editable cell did not contain correct classname", - "not-editable", - editor.findElement(By.className("v-grid-editor-cells")) - .findElements(By.xpath("./div")).get(3) - .getAttribute("class")); + selectMenuPath("Component", "Filter", "Column 1 starts with \"(23\""); + assertEditorClosed(); } - private WebElement getSaveButton() { - return getDriver().findElement(By.className("v-grid-editor-save")); + protected WebElement getSaveButton() { + return getDriver().findElement(BY_EDITOR_SAVE); } - private WebElement getCancelButton() { - return getDriver().findElement(By.className("v-grid-editor-cancel")); + protected WebElement getCancelButton() { + return getDriver().findElement(BY_EDITOR_CANCEL); } } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorUnbufferedTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorUnbufferedTest.java new file mode 100644 index 0000000000..08094b57e3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorUnbufferedTest.java @@ -0,0 +1,223 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.testbench.elements.GridElement.GridCellElement; + +public class GridEditorUnbufferedTest extends GridEditorTest { + + private static final String[] TOGGLE_EDITOR_BUFFERED = new String[] { + "Component", "Editor", "Buffered mode" }; + private static final String[] CANCEL_EDIT = new String[] { "Component", + "Editor", "Cancel edit" }; + + @Override + @Before + public void setUp() { + super.setUp(); + selectMenuPath(TOGGLE_EDITOR_BUFFERED); + } + + @Test + public void testEditorShowsNoButtons() { + selectMenuPath(EDIT_ITEM_5); + + assertEditorOpen(); + + assertFalse("Save button should not be visible in unbuffered mode.", + isElementPresent(BY_EDITOR_SAVE)); + + assertFalse("Cancel button should not be visible in unbuffered mode.", + isElementPresent(BY_EDITOR_CANCEL)); + } + + @Test + public void testToggleEditorUnbufferedWhileOpen() { + selectMenuPath(EDIT_ITEM_5); + assertEditorOpen(); + selectMenuPath(TOGGLE_EDITOR_BUFFERED); + boolean thrown = logContainsText("Exception occured, java.lang.IllegalStateException"); + assertTrue("IllegalStateException thrown", thrown); + } + + @Test + public void testEditorMove() { + selectMenuPath(EDIT_ITEM_5); + + assertEditorOpen(); + + String firstFieldValue = getEditorWidgets().get(0) + .getAttribute("value"); + assertEquals("Editor should be at row 5", "(5, 0)", firstFieldValue); + + getGridElement().getCell(10, 0).click(); + firstFieldValue = getEditorWidgets().get(0).getAttribute("value"); + + assertEquals("Editor should be at row 10", "(10, 0)", firstFieldValue); + } + + @Test + public void testValidationErrorPreventsMove() { + // Because of "out of view" issues, we need to move this for easy access + selectMenuPath("Component", "Columns", "Column 7", "Column 7 Width", + "50px"); + for (int i = 0; i < 6; ++i) { + selectMenuPath("Component", "Columns", "Column 7", "Move left"); + } + + selectMenuPath(EDIT_ITEM_5); + + getEditorWidgets().get(1).click(); + getEditorWidgets().get(1).sendKeys("not a number"); + + getGridElement().getCell(10, 0).click(); + + assertEquals("Editor should not move from row 5", "(5, 0)", + getEditorWidgets().get(0).getAttribute("value")); + } + + @Test + public void testErrorMessageWrapperHidden() { + selectMenuPath(EDIT_ITEM_5); + + assertEditorOpen(); + + WebElement editorFooter = getEditor().findElement( + By.className("v-grid-editor-footer")); + + assertTrue("Editor footer should not be visible when there's no error", + editorFooter.getCssValue("display").equalsIgnoreCase("none")); + } + + @Test + public void testScrollEnabledOnProgrammaticOpen() { + int originalScrollPos = getGridVerticalScrollPos(); + + selectMenuPath(EDIT_ITEM_5); + + scrollGridVerticallyTo(100); + assertGreater( + "Grid should scroll vertically while editing in unbuffered mode", + getGridVerticalScrollPos(), originalScrollPos); + } + + @Test + public void testScrollEnabledOnMouseOpen() { + int originalScrollPos = getGridVerticalScrollPos(); + + GridCellElement cell_5_0 = getGridElement().getCell(5, 0); + new Actions(getDriver()).doubleClick(cell_5_0).perform(); + + scrollGridVerticallyTo(100); + assertGreater( + "Grid should scroll vertically while editing in unbuffered mode", + getGridVerticalScrollPos(), originalScrollPos); + } + + @Test + public void testScrollEnabledOnKeyboardOpen() { + int originalScrollPos = getGridVerticalScrollPos(); + + GridCellElement cell_5_0 = getGridElement().getCell(5, 0); + cell_5_0.click(); + new Actions(getDriver()).sendKeys(Keys.ENTER).perform(); + + scrollGridVerticallyTo(100); + assertGreater( + "Grid should scroll vertically while editing in unbuffered mode", + getGridVerticalScrollPos(), originalScrollPos); + } + + @Test + public void testEditorInDisabledGrid() { + selectMenuPath(EDIT_ITEM_5); + + selectMenuPath("Component", "State", "Enabled"); + assertEditorOpen(); + + assertTrue("Editor text field should be disabled", + null != getEditorWidgets().get(2).getAttribute("disabled")); + + selectMenuPath("Component", "State", "Enabled"); + assertEditorOpen(); + + assertFalse("Editor text field should not be disabled", + null != getEditorWidgets().get(2).getAttribute("disabled")); + } + + @Test + public void testMouseOpeningClosing() { + + getGridElement().getCell(4, 0).doubleClick(); + assertEditorOpen(); + + selectMenuPath(CANCEL_EDIT); + selectMenuPath(TOGGLE_EDIT_ENABLED); + + getGridElement().getCell(4, 0).doubleClick(); + assertEditorClosed(); + } + + @Test + public void testProgrammaticOpeningWhenOpen() { + selectMenuPath(EDIT_ITEM_5); + assertEditorOpen(); + assertEquals("Editor should edit row 5", "(5, 0)", getEditorWidgets() + .get(0).getAttribute("value")); + + selectMenuPath(EDIT_ITEM_100); + assertEditorOpen(); + assertEquals("Editor should edit row 100", "(100, 0)", + getEditorWidgets().get(0).getAttribute("value")); + } + + @Test + public void testExternalValueChangePassesToEditor() { + selectMenuPath(EDIT_ITEM_5); + assertEditorOpen(); + + selectMenuPath("Component", "State", "ReactiveValueChanger"); + + getEditorWidgets().get(0).click(); + getEditorWidgets().get(0).sendKeys("changing value"); + + // Focus another field to cause the value to be sent to the server + getEditorWidgets().get(2).click(); + + assertEquals("Value of Column 2 in the editor was not changed", + "Modified", getEditorWidgets().get(2).getAttribute("value")); + } + + @Test + public void testEditorClosedOnUserSort() { + selectMenuPath(EDIT_ITEM_5); + + getGridElement().getHeaderCell(0, 0).click(); + + assertEditorClosed(); + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridFocusTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridFocusTest.java new file mode 100644 index 0000000000..ca9d78409c --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridFocusTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.grid.basicfeatures.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.testbench.elements.MenuBarElement; +import com.vaadin.tests.components.grid.basicfeatures.GridBasicFeaturesTest; + +/** + * Test for server-side Grid focus features. + * + * @since + * @author Vaadin Ltd + */ +public class GridFocusTest extends GridBasicFeaturesTest { + + @Before + public void setUp() { + openTestURL(); + } + + @Test + public void testFocusListener() { + selectMenuPath("Component", "Listeners", "Focus listener"); + + getGridElement().click(); + + assertTrue("Focus listener should be invoked", + getLogRow(0).contains("FocusEvent")); + } + + @Test + public void testBlurListener() { + selectMenuPath("Component", "Listeners", "Blur listener"); + + getGridElement().click(); + $(MenuBarElement.class).first().click(); + + assertTrue("Blur listener should be invoked", + getLogRow(0).contains("BlurEvent")); + } + + @Test + public void testProgrammaticFocus() { + selectMenuPath("Component", "State", "Set focus"); + + assertTrue("Grid cell (0, 0) should be focused", getGridElement() + .getCell(0, 0).isFocused()); + } + + @Test + public void testTabIndex() { + assertEquals(getGridElement().getAttribute("tabindex"), "0"); + + selectMenuPath("Component", "State", "Tab index", "-1"); + assertEquals(getGridElement().getAttribute("tabindex"), "-1"); + + selectMenuPath("Component", "State", "Tab index", "10"); + assertEquals(getGridElement().getAttribute("tabindex"), "10"); + } +} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java index 9953bbcae0..8bf8639d76 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSelectionTest.java @@ -20,8 +20,10 @@ import static org.junit.Assert.assertTrue; import org.junit.Test; import org.openqa.selenium.Keys; +import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.ui.ExpectedCondition; import com.vaadin.testbench.By; import com.vaadin.testbench.elements.GridElement; @@ -335,6 +337,62 @@ public class GridSelectionTest extends GridBasicFeaturesTest { getRow(5).isSelected()); } + @Test + public void testSelectionCheckBoxesHaveStyleNames() { + openTestURL(); + + setSelectionModelMulti(); + + assertTrue( + "Selection column CheckBox should have the proper style name set", + getGridElement().getCell(0, 0).findElement(By.tagName("span")) + .getAttribute("class") + .contains("v-grid-selection-checkbox")); + + GridCellElement header = getGridElement().getHeaderCell(0, 0); + assertTrue("Select all CheckBox should have the proper style name set", + header.findElement(By.tagName("span")).getAttribute("class") + .contains("v-grid-select-all-checkbox")); + } + + @Test + public void testServerSideSelectTogglesSelectAllCheckBox() { + openTestURL(); + + setSelectionModelMulti(); + GridCellElement header = getGridElement().getHeaderCell(0, 0); + + WebElement selectAll = header.findElement(By.tagName("input")); + + selectMenuPath("Component", "State", "Select all"); + waitUntilCheckBoxValue(selectAll, true); + assertTrue("Select all CheckBox wasn't selected as expected", + selectAll.isSelected()); + + selectMenuPath("Component", "State", "Select none"); + waitUntilCheckBoxValue(selectAll, false); + assertFalse("Select all CheckBox was selected unexpectedly", + selectAll.isSelected()); + + selectMenuPath("Component", "State", "Select all"); + waitUntilCheckBoxValue(selectAll, true); + getGridElement().getCell(5, 0).click(); + waitUntilCheckBoxValue(selectAll, false); + assertFalse("Select all CheckBox was selected unexpectedly", + selectAll.isSelected()); + } + + private void waitUntilCheckBoxValue(final WebElement checkBoxElememnt, + final boolean expectedValue) { + waitUntil(new ExpectedCondition<Boolean>() { + @Override + public Boolean apply(WebDriver input) { + return expectedValue ? checkBoxElememnt.isSelected() + : !checkBoxElememnt.isSelected(); + } + }, 5); + } + private void setSelectionModelMulti() { selectMenuPath("Component", "State", "Selection mode", "multi"); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java index 0e5dd32989..238b470feb 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridSidebarThemeTest.java @@ -45,15 +45,15 @@ public class GridSidebarThemeTest extends GridBasicFeaturesTest { private void runTestSequence(String theme) throws IOException { openTestURL("theme=" + theme); - compareScreen(theme + "_SidebarClosed"); + compareScreen(theme + "-SidebarClosed"); getSidebarOpenButton().click(); - compareScreen(theme + "_SidebarOpen"); + compareScreen(theme + "-SidebarOpen"); new Actions(getDriver()).moveToElement(getColumnHidingToggle(2), 5, 5) .perform(); - compareScreen(theme + "_OnMouseOverNotHiddenToggle"); + compareScreen(theme + "-OnMouseOverNotHiddenToggle"); getColumnHidingToggle(2).click(); getColumnHidingToggle(3).click(); @@ -63,17 +63,17 @@ public class GridSidebarThemeTest extends GridBasicFeaturesTest { .perform(); ; - compareScreen(theme + "_TogglesTriggered"); + compareScreen(theme + "-TogglesTriggered"); new Actions(getDriver()).moveToElement(getColumnHidingToggle(2)) .perform(); ; - compareScreen(theme + "_OnMouseOverHiddenToggle"); + compareScreen(theme + "-OnMouseOverHiddenToggle"); getSidebarOpenButton().click(); - compareScreen(theme + "_SidebarClosed2"); + compareScreen(theme + "-SidebarClosed2"); } @Override diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java index 5d72d481c7..f44f39689c 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStructureTest.java @@ -274,7 +274,7 @@ public class GridStructureTest extends GridBasicFeaturesTest { @Test public void testBareItemSetChangeRemovingAllRows() throws Exception { openTestURL(); - selectMenuPath("Component", "Filter", "Add impassable filter"); + selectMenuPath("Component", "Filter", "Impassable filter"); assertFalse("A notification shouldn't have been displayed", $(NotificationElement.class).exists()); assertTrue("No body cells should've been found", getGridElement() diff --git a/uitest/src/com/vaadin/tests/components/popupview/PopupViewShortcutActionHandlerTest.java b/uitest/src/com/vaadin/tests/components/popupview/PopupViewShortcutActionHandlerTest.java index 3005365c47..6155820990 100644 --- a/uitest/src/com/vaadin/tests/components/popupview/PopupViewShortcutActionHandlerTest.java +++ b/uitest/src/com/vaadin/tests/components/popupview/PopupViewShortcutActionHandlerTest.java @@ -15,13 +15,15 @@ */ package com.vaadin.tests.components.popupview; -import com.vaadin.tests.tb3.MultiBrowserTest; import org.junit.Assert; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; +import com.vaadin.testbench.parallel.BrowserUtil; +import com.vaadin.tests.tb3.MultiBrowserTest; + /** * Check availability of shortcut action listener in the popup view. * @@ -29,6 +31,14 @@ import org.openqa.selenium.WebElement; */ public class PopupViewShortcutActionHandlerTest extends MultiBrowserTest { + @Override + protected boolean requireWindowFocusForIE() { + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + return false; + } + return true; + } + @Test public void testShortcutHandling() { openTestURL(); diff --git a/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java new file mode 100644 index 0000000000..0dc371e57c --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckbox.java @@ -0,0 +1,58 @@ +package com.vaadin.tests.components.splitpanel; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.event.FieldEvents.TextChangeEvent; +import com.vaadin.event.FieldEvents.TextChangeListener; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.AbstractTextField.TextChangeEventMode; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.GridLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.TextField; +import com.vaadin.ui.UI; +import com.vaadin.ui.Window; + +@Theme("reindeer") +public class GridLayoutWithCheckbox extends UI { + + @Override + protected void init(VaadinRequest request) { + GridLayout grid = new GridLayout(2, 3); + grid.setWidth(500, Unit.PIXELS); + + Label l = new Label("Textfield 1:"); + grid.addComponent(l, 0, 0); + TextField textfield = new TextField(); + textfield.addTextChangeListener(new TextChangeListener() { + + @Override + public void textChange(TextChangeEvent event) { + + } + }); + textfield.setTextChangeEventMode(TextChangeEventMode.EAGER); + grid.addComponent(textfield, 1, 0); + + l = new Label("CheckBox:"); + grid.addComponent(l, 0, 1); + CheckBox checkBox = new CheckBox(); + grid.addComponent(checkBox, 1, 2); + checkBox.addValueChangeListener(new ValueChangeListener() { + + @Override + public void valueChange(ValueChangeEvent event) { + + } + }); + Window window = new Window(); + window.setWidth(300.0f, Unit.PIXELS); + window.setContent(grid); + window.setResizable(false); + window.setWidth(550, Unit.PIXELS); + + // grid.setColumnExpandRatio(1, 1); + addWindow(window); + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckboxTest.java b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckboxTest.java new file mode 100644 index 0000000000..fee46c155a --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/splitpanel/GridLayoutWithCheckboxTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.splitpanel; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.CheckBoxElement; +import com.vaadin.testbench.elements.TextFieldElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class GridLayoutWithCheckboxTest extends MultiBrowserTest { + + private TextFieldElement tf; + private WebElement tfSlot; + private CheckBoxElement cb; + private WebElement cbSlot; + private Dimension tfSize; + private Dimension tfSlotSize; + private Dimension cbSize; + private Dimension cbSlotSize; + + @Test + public void layoutShouldStayTheSame() { + openTestURL(); + tf = $(TextFieldElement.class).first(); + tfSlot = tf.findElement(By.xpath("..")); + cb = $(CheckBoxElement.class).first(); + cbSlot = cb.findElement(By.xpath("..")); + + // Doing anything with the textfield or checkbox should not affect + // layout + + tf.setValue("a"); + assertSizes(); + cb.click(); + assertSizes(); + tf.setValue("b"); + assertSizes(); + cb.click(); + assertSizes(); + + } + + private void assertSizes() { + if (tfSize == null) { + tfSize = tf.getSize(); + tfSlotSize = tfSlot.getSize(); + cbSize = cb.getSize(); + cbSlotSize = cbSlot.getSize(); + } else { + Assert.assertEquals(tfSize, tf.getSize()); + Assert.assertEquals(tfSlotSize, tfSlot.getSize()); + Assert.assertEquals(cbSize, cb.getSize()); + Assert.assertEquals(cbSlotSize, cbSlot.getSize()); + } + + } +} diff --git a/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java index 08b65e2ef5..8d1a9fc7df 100644 --- a/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java +++ b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.java @@ -2,21 +2,22 @@ package com.vaadin.tests.components.table; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; -import com.vaadin.tests.components.TestBase; -import com.vaadin.ui.Alignment; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Table; -import com.vaadin.ui.TextField; +import com.vaadin.ui.Table.ColumnCollapseEvent; +import com.vaadin.ui.Table.ColumnCollapseListener; -public class ColumnCollapsingAndColumnExpansion extends TestBase { +public class ColumnCollapsingAndColumnExpansion extends AbstractTestUIWithLog { private Table table; @Override - public void setup() { + protected void setup(VaadinRequest request) { table = new Table(); @@ -50,41 +51,44 @@ public class ColumnCollapsingAndColumnExpansion extends TestBase { "cell " + 2 + "-" + y, "cell " + 3 + "-" + y, }, new Object()); } - - addComponent(table); - - HorizontalLayout hl = new HorizontalLayout(); - final TextField tf = new TextField("Column name (ColX)"); - Button hide = new Button("Collapse", new ClickListener() { + table.addColumnCollapseListener(new ColumnCollapseListener() { @Override - public void buttonClick(ClickEvent event) { - table.setColumnCollapsed(tf.getValue(), true); - } - - }); + public void columnCollapseStateChange(ColumnCollapseEvent event) { + log("Collapse state for " + event.getPropertyId() + + " changed to " + + table.isColumnCollapsed(event.getPropertyId())); - Button show = new Button("Show", new ClickListener() { - - @Override - public void buttonClick(ClickEvent event) { - table.setColumnCollapsed(tf.getValue(), false); } - }); + addComponent(table); - hl.addComponent(tf); - hl.addComponent(hide); - hl.addComponent(show); - hl.setComponentAlignment(tf, Alignment.BOTTOM_LEFT); - hl.setComponentAlignment(hide, Alignment.BOTTOM_LEFT); - hl.setComponentAlignment(show, Alignment.BOTTOM_LEFT); - addComponent(hl); + for (int i = 1; i <= 3; i++) { + HorizontalLayout hl = new HorizontalLayout(); + final String id = "Col" + i; + Button hide = new Button("Collapse " + id, new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + table.setColumnCollapsed(id, true); + } + }); + + Button show = new Button("Show " + id, new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + table.setColumnCollapsed(id, false); + } + }); + + hl.addComponent(hide); + hl.addComponent(show); + addComponent(hl); + } } @Override - protected String getDescription() { + protected String getTestDescription() { return "After hiding column 2 the remaining columns (1 and 3) should use all available space in the table"; } diff --git a/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansionTest.java b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansionTest.java new file mode 100644 index 0000000000..739851de2f --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansionTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.table; + +import java.io.IOException; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.TestBenchElement; +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.parallel.BrowserUtil; +import com.vaadin.tests.components.table.CustomTableElement.ContextMenuElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class ColumnCollapsingAndColumnExpansionTest extends MultiBrowserTest { + + @Test + public void expandCorrectlyAfterCollapse() throws IOException { + openTestURL(); + + CustomTableElement table = $(CustomTableElement.class).first(); + + // Hide col2 through UI + table.openCollapseMenu().getItem(1).click(); + compareScreen(table, "col1-col3"); + + // Hide col1 using button + ButtonElement hide1 = $(ButtonElement.class).caption("Collapse Col1") + .first(); + hide1.click(); + compareScreen(table, "col3"); + + // Show column 2 using context menu (first action) + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + // IE8 can context click but the popup is off screen so it can't be + // interacted with... + ButtonElement show2 = $(ButtonElement.class).caption("Show Col2") + .first(); + show2.click(); + } else { + contextClick(table.getCell(0, 0)); + ContextMenuElement contextMenu = table.getContextMenu(); + WebElement i = contextMenu.getItem(0); + i.click(); + } + compareScreen(table, "col2-col3"); + + // Show column 1 again + ButtonElement show1 = $(ButtonElement.class).caption("Show Col1") + .first(); + show1.click(); + + compareScreen(table, "col1-col2-col3"); + } + + private void contextClick(TestBenchElement e) { + if (e.isPhantomJS()) { + JavascriptExecutor js = e.getCommandExecutor(); + String scr = "var element=arguments[0];" + + "var ev = document.createEvent('HTMLEvents');" + + "ev.initEvent('contextmenu', true, false);" + + "element.dispatchEvent(ev);"; + js.executeScript(scr, e); + } else { + e.contextClick(); + } + + } + + @Test + public void collapseEvents() { + openTestURL(); + CustomTableElement table = $(CustomTableElement.class).first(); + + // Through menu + table.openCollapseMenu().getItem(0).click(); + Assert.assertEquals("1. Collapse state for Col1 changed to true", + getLogRow(0)); + + // Through button + $(ButtonElement.class).caption("Collapse Col2").first().click(); + Assert.assertEquals("2. Collapse state for Col2 changed to true", + getLogRow(0)); + + // Show through menu + table.openCollapseMenu().getItem(1).click(); + Assert.assertEquals("3. Collapse state for Col1 changed to false", + getLogRow(0)); + + // Show through button + $(ButtonElement.class).caption("Show Col2").first().click(); + Assert.assertEquals("4. Collapse state for Col2 changed to false", + getLogRow(0)); + + } +} diff --git a/uitest/src/com/vaadin/tests/components/table/CustomTableElement.java b/uitest/src/com/vaadin/tests/components/table/CustomTableElement.java new file mode 100644 index 0000000000..f1df98fb38 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/CustomTableElement.java @@ -0,0 +1,57 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.table; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.TableElement; +import com.vaadin.testbench.elementsbase.AbstractElement; +import com.vaadin.testbench.elementsbase.ServerClass; + +@ServerClass("com.vaadin.ui.Table") +public class CustomTableElement extends TableElement { + + public CollapseMenu openCollapseMenu() { + getCollapseMenuToggle().click(); + WebElement cm = getDriver().findElement( + By.xpath("//*[@id='PID_VAADIN_CM']")); + return wrapElement(cm, getCommandExecutor()).wrap(CollapseMenu.class); + } + + public static class CollapseMenu extends ContextMenuElement { + } + + public WebElement getCollapseMenuToggle() { + return findElement(By.className("v-table-column-selector")); + } + + public static class ContextMenuElement extends AbstractElement { + + public WebElement getItem(int index) { + return findElement(By.xpath(".//table//tr[" + (index + 1) + + "]//td/*")); + } + + } + + public ContextMenuElement getContextMenu() { + WebElement cm = getDriver().findElement(By.className("v-contextmenu")); + return wrapElement(cm, getCommandExecutor()).wrap( + ContextMenuElement.class); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/table/TableColumnWidthsAndSorting.java b/uitest/src/com/vaadin/tests/components/table/TableColumnWidthsAndSorting.java new file mode 100644 index 0000000000..8f0a8803f3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/TableColumnWidthsAndSorting.java @@ -0,0 +1,50 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.table; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.tests.fieldgroup.ComplexPerson; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Table; + +@Theme("valo") +public class TableColumnWidthsAndSorting extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + final Table t = new Table(); + t.setContainerDataSource(ComplexPerson.createContainer(100)); + t.setVisibleColumns("firstName", "lastName", "age", "gender", "salary"); + t.setColumnWidth("firstName", 200); + t.setColumnWidth("lastName", 200); + t.setSelectable(true); + addComponent(t); + + Button b = new Button("Sort according to gender", new ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + t.sort(new Object[] { "gender" }, new boolean[] { true }); + } + }); + + addComponent(b); + } +} diff --git a/uitest/src/com/vaadin/tests/components/table/TableColumnWidthsAndSortingTest.java b/uitest/src/com/vaadin/tests/components/table/TableColumnWidthsAndSortingTest.java new file mode 100644 index 0000000000..9c49b3d0b6 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/TableColumnWidthsAndSortingTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.table; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.testbench.elements.TableElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class TableColumnWidthsAndSortingTest extends MultiBrowserTest { + + @Test + public void testHeaderHeight() { + openTestURL(); + TableElement t = $(TableElement.class).first(); + + assertHeaderCellHeight(t); + + // Sort according to age + t.getHeaderCell(2).click(); + assertHeaderCellHeight(t); + + // Sort again according to age + t.getHeaderCell(2).click(); + assertHeaderCellHeight(t); + + } + + private void assertHeaderCellHeight(TableElement t) { + // Assert all headers are correct height (37px according to default + // Valo) + for (int i = 0; i < 5; i++) { + Assert.assertEquals("Height of header cell " + i + " is wrong", 37, + t.getHeaderCell(0).getSize().getHeight()); + } + + } +} diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetInSplitPanel.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetInSplitPanel.java new file mode 100644 index 0000000000..b2313020a3 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetInSplitPanel.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.tabsheet; + +import com.vaadin.annotations.Theme; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Label; +import com.vaadin.ui.TabSheet; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalSplitPanel; +import com.vaadin.ui.themes.ValoTheme; + +@Theme("valo") +public class TabSheetInSplitPanel extends UI { + + @Override + protected void init(VaadinRequest request) { + VerticalSplitPanel verticalSplitter = new VerticalSplitPanel(); + setContent(verticalSplitter); + verticalSplitter.setSizeFull(); + TabSheet t = new TabSheet(); + t.setHeight("100%"); + t.addTab(new Label("Hello in tab"), "Hello tab"); + t.setStyleName(ValoTheme.TABSHEET_FRAMED); + verticalSplitter.addComponent(t); + verticalSplitter.addComponent(new Label("Hello")); + + } + +} diff --git a/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetInSplitPanelTest.java b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetInSplitPanelTest.java new file mode 100644 index 0000000000..8070133bde --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/tabsheet/TabSheetInSplitPanelTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.tabsheet; + +import java.util.List; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.TabSheetElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class TabSheetInSplitPanelTest extends MultiBrowserTest { + + @Test + public void ensureNoScrollbars() { + openTestURL(); + TabSheetElement ts = $(TabSheetElement.class).first(); + List<WebElement> scrollables = ts.findElements(By + .xpath("//*[contains(@class,'v-scrollable')]")); + for (WebElement scrollable : scrollables) { + assertNoHorizontalScrollbar(scrollable, + "Element should not have a horizontal scrollbar"); + assertNoVerticalScrollbar(scrollable, + "Element should not have a vertical scrollbar"); + } + } + +} diff --git a/uitest/src/com/vaadin/tests/components/treetable/TreeTableScrollOnExpand.java b/uitest/src/com/vaadin/tests/components/treetable/TreeTableScrollOnExpand.java new file mode 100644 index 0000000000..07cf7f8c2e --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/treetable/TreeTableScrollOnExpand.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.treetable; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.TreeTable; + +public class TreeTableScrollOnExpand extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + TreeTable t = new TreeTable(); + t.setSelectable(true); + t.setImmediate(true); + t.setSizeFull(); + t.addContainerProperty("Name", String.class, "null"); + for (int i = 1; i <= 100; i++) { + String parentID = "Item " + i; + Object parent = t.addItem(new Object[] { parentID }, parentID); + String childID = "Item " + (100 + i); + Object child = t.addItem(new Object[] { childID }, childID); + t.getContainerDataSource().setParent(childID, parentID); + } + addComponent(t); + } + + @Override + public Integer getTicketNumber() { + return 18247; + } + + @Override + public String getTestDescription() { + return "After selecting an item and scrolling it out of view, TreeTable should not scroll to the " + + "selected item when expanding an item."; + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/treetable/TreeTableScrollOnExpandTest.java b/uitest/src/com/vaadin/tests/components/treetable/TreeTableScrollOnExpandTest.java new file mode 100644 index 0000000000..a17559cc81 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/treetable/TreeTableScrollOnExpandTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.treetable; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.TreeTableElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class TreeTableScrollOnExpandTest extends MultiBrowserTest { + + @Test + public void testScrollOnExpand() throws InterruptedException, IOException { + openTestURL(); + TreeTableElement tt = $(TreeTableElement.class).first(); + tt.getRow(0).click(); + tt.scroll(300); + sleep(1000); + tt.getRow(20).toggleExpanded(); + // Need to wait a bit to avoid accepting the case where the TreeTable is + // in the desired state only for a short while. + sleep(1000); + WebElement focusedRow = getDriver().findElement( + By.className("v-table-focus")); + assertEquals("Item 21", focusedRow.getText()); + } +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/components/window/GridInWindow.java b/uitest/src/com/vaadin/tests/components/window/GridInWindow.java new file mode 100644 index 0000000000..918a991cc1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/GridInWindow.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.window; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Button; +import com.vaadin.ui.Grid; +import com.vaadin.ui.Window; + +public class GridInWindow extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + final Grid grid = new Grid(); + + grid.addColumn("Hidable column").setHidable(true); + grid.addRow("Close and reopen and it vanishes"); + + Button popupButton = new Button("Open PopUp", + new Button.ClickListener() { + @Override + public void buttonClick(Button.ClickEvent event) { + Window subWindow = new Window("Sub-window"); + subWindow.setContent(grid); + subWindow.setWidth(600, Unit.PIXELS); + subWindow.setWidth(400, Unit.PIXELS); + getUI().addWindow(subWindow); + } + }); + + addComponent(popupButton); + + } + +} diff --git a/uitest/src/com/vaadin/tests/components/window/GridInWindowTest.java b/uitest/src/com/vaadin/tests/components/window/GridInWindowTest.java new file mode 100644 index 0000000000..301a7c60e4 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/GridInWindowTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.window; + +import org.junit.Test; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.tests.tb3.SingleBrowserTest; +import com.vaadin.tests.tb3.newelements.WindowElement; + +public class GridInWindowTest extends SingleBrowserTest { + + @Test + public void ensureAttachInHierachyChange() { + openTestURL("debug"); + $(ButtonElement.class).first().click(); + assertNoErrorNotifications(); + $(WindowElement.class).first().close(); + assertNoErrorNotifications(); + $(ButtonElement.class).first().click(); + assertNoErrorNotifications(); + } +} diff --git a/uitest/src/com/vaadin/tests/components/window/OpenModalWindowAndFocusField.java b/uitest/src/com/vaadin/tests/components/window/OpenModalWindowAndFocusField.java new file mode 100644 index 0000000000..1c82a3de02 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/OpenModalWindowAndFocusField.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.window; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.TextArea; +import com.vaadin.ui.Window; + +public class OpenModalWindowAndFocusField extends AbstractTestUIWithLog { + + @Override + protected void setup(VaadinRequest request) { + Button button = new Button("Open modal and focus textarea"); + button.setId("openFocus"); + button.addClickListener(new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + open(true); + } + }); + addComponent(button); + + button = new Button("Only open modal"); + button.setId("open"); + button.addClickListener(new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + open(false); + } + + }); + addComponent(button); + + } + + private void open(boolean focus) { + Window wind = new Window(); + wind.setModal(true); + TextArea ta = new TextArea(); + wind.setContent(ta); + addWindow(wind); + if (focus) { + ta.focus(); + } + } +} diff --git a/uitest/src/com/vaadin/tests/components/window/OpenModalWindowAndFocusFieldTest.java b/uitest/src/com/vaadin/tests/components/window/OpenModalWindowAndFocusFieldTest.java new file mode 100644 index 0000000000..5dba1c3285 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/OpenModalWindowAndFocusFieldTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.window; + +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.testbench.elements.TextAreaElement; +import com.vaadin.tests.tb3.MultiBrowserTest; + +public class OpenModalWindowAndFocusFieldTest extends MultiBrowserTest { + + @Test + public void openModalAndFocusField() { + openTestURL(); + $(ButtonElement.class).id("openFocus").click(); + TextAreaElement textArea = $(TextAreaElement.class).first(); + + assertElementsEquals(textArea, getActiveElement()); + } + + @Test + public void openModal() { + openTestURL(); + $(ButtonElement.class).id("open").click(); + // WindowElement window = $(WindowElement.class).first(); + WebElement windowFocusElement = findElement(By + .xpath("//div[@class='v-window-contents']/div[@class='v-scrollable']")); + + assertElementsEquals(windowFocusElement, getActiveElement()); + } + +} diff --git a/uitest/src/com/vaadin/tests/components/window/WindowCloseShortcuts.java b/uitest/src/com/vaadin/tests/components/window/WindowCloseShortcuts.java new file mode 100644 index 0000000000..d9c22a26ee --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/window/WindowCloseShortcuts.java @@ -0,0 +1,199 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.components.window; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Collections; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Attribute; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; + +import com.vaadin.annotations.Theme; +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Button.ClickListener; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; +import com.vaadin.ui.RichTextArea; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.Window; +import com.vaadin.ui.declarative.Design; +import com.vaadin.ui.declarative.DesignContext; + +@Theme("valo") +@SuppressWarnings("serial") +public class WindowCloseShortcuts extends AbstractTestUI { + + private Window window; + private Label designLabel; + private VerticalLayout buttonLayout; + + @Override + protected void setup(VaadinRequest request) { + getLayout().setSpacing(true); + + window = new Window("Test window"); + window.setVisible(true); + window.setModal(true); + window.setContent(new RichTextArea()); + + Panel buttonPanel = new Panel(); + buttonLayout = new VerticalLayout(); + buttonLayout.setSizeFull(); + buttonPanel.setCaption("Demo controls"); + + addButton(new Button("Open window", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + UI.getCurrent().addWindow(window); + window.center(); + updateDesign(); + } + })); + + addButton(new Button("Add ENTER close shortcut", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + window.addCloseShortcut(KeyCode.ENTER); + updateDesign(); + } + })); + + addButton(new Button("Add TAB close shortcut", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + window.addCloseShortcut(KeyCode.TAB); + updateDesign(); + } + })); + + addButton(new Button("Remove ESC close shortcut", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + window.removeCloseShortcut(KeyCode.ESCAPE); + updateDesign(); + } + })); + + addButton(new Button("Clear all close shortcuts", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + window.removeAllCloseShortcuts(); + updateDesign(); + } + })); + + addButton(new Button("Reset to default state", new ClickListener() { + @Override + @SuppressWarnings("deprecation") + public void buttonClick(ClickEvent event) { + window.removeCloseShortcut(); + updateDesign(); + } + })); + + buttonPanel.setContent(buttonLayout); + buttonPanel.setSizeUndefined(); + addComponent(buttonPanel); + buttonPanel.setWidth("400px"); + + Panel designPanel = new Panel(); + designPanel.setCaption("Window design"); + designLabel = new Label(""); + VerticalLayout designLayout = new VerticalLayout(); + designLayout.addComponent(designLabel); + designPanel.setContent(designLayout); + addComponent(designPanel); + + updateDesign(); + } + + private void addButton(Button b) { + b.setWidth("100%"); + buttonLayout.addComponent(b); + } + + // + // The following code is adapted from DeclarativeTestBaseBase.java + // (that's not a typo) + // + + private void updateDesign() { + String design = ""; + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + DesignContext dc = new DesignContext(); + dc.setRootComponent(window); + Design.write(dc, outputStream); + design = outputStream.toString("UTF-8"); + } catch (Exception e) { + return; + } + Element producedElem = Jsoup.parse(design).body().child(0); + design = elementToHtml(producedElem); + designLabel.setCaption(design); + } + + // + // The following code is copied directly from DeclarativeTestBaseBase.java + // (that's not a typo, either) + // + + private String elementToHtml(Element producedElem) { + StringBuilder stringBuilder = new StringBuilder(); + elementToHtml(producedElem, stringBuilder); + return stringBuilder.toString(); + } + + private String elementToHtml(Element producedElem, StringBuilder sb) { + ArrayList<String> names = new ArrayList<String>(); + for (Attribute a : producedElem.attributes().asList()) { + names.add(a.getKey()); + } + Collections.sort(names); + + sb.append("<" + producedElem.tagName() + ""); + for (String attrName : names) { + sb.append(" ").append(attrName).append("=").append("\'") + .append(producedElem.attr(attrName)).append("\'"); + } + sb.append(">"); + for (Node child : producedElem.childNodes()) { + if (child instanceof Element) { + elementToHtml((Element) child, sb); + } else if (child instanceof TextNode) { + String text = ((TextNode) child).text(); + sb.append(text.trim()); + } + } + sb.append("</").append(producedElem.tagName()).append(">"); + return sb.toString(); + } + + @Override + protected Integer getTicketNumber() { + return 17383; + } + +} diff --git a/uitest/src/com/vaadin/tests/debug/PreserveCustomDebugSectionOpen.java b/uitest/src/com/vaadin/tests/debug/PreserveCustomDebugSectionOpen.java new file mode 100644 index 0000000000..0ac2e87510 --- /dev/null +++ b/uitest/src/com/vaadin/tests/debug/PreserveCustomDebugSectionOpen.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.debug; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.widgetset.TestingWidgetSet; +import com.vaadin.ui.Label; + +@Widgetset(TestingWidgetSet.NAME) +public class PreserveCustomDebugSectionOpen extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + addComponent(new Label( + "UI for testing that a custom debug window section remains open after refreshging.")); + } + +} diff --git a/uitest/src/com/vaadin/tests/debug/PreserveCustomDebugSectionOpenTest.java b/uitest/src/com/vaadin/tests/debug/PreserveCustomDebugSectionOpenTest.java new file mode 100644 index 0000000000..01b73ded92 --- /dev/null +++ b/uitest/src/com/vaadin/tests/debug/PreserveCustomDebugSectionOpenTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.debug; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; + +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class PreserveCustomDebugSectionOpenTest extends SingleBrowserTest { + @Test + public void testPreserveSection() { + setDebug(true); + openTestURL(); + + findElement( + By.cssSelector(".v-debugwindow-tabs button[title=\"Dummy debug window section\"]")) + .click(); + + WebElement content = findElement(By + .cssSelector(".v-debugwindow-content")); + + // Sanity check + Assert.assertEquals("Dummy debug window section", content.getText()); + + // Open page again, should still have the same section open + openTestURL(); + + content = findElement(By.cssSelector(".v-debugwindow-content")); + Assert.assertEquals("Dummy debug window section", content.getText()); + } +} diff --git a/uitest/src/com/vaadin/tests/debug/ProfilerZeroOverhead.java b/uitest/src/com/vaadin/tests/debug/ProfilerZeroOverhead.java new file mode 100644 index 0000000000..7daf38a0e5 --- /dev/null +++ b/uitest/src/com/vaadin/tests/debug/ProfilerZeroOverhead.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.debug; + +import com.vaadin.annotations.Widgetset; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.tests.widgetset.TestingWidgetSet; +import com.vaadin.tests.widgetset.client.ProfilerCompilationCanary; +import com.vaadin.tests.widgetset.server.TestWidgetComponent; + +@Widgetset(TestingWidgetSet.NAME) +public class ProfilerZeroOverhead extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + addComponent(new TestWidgetComponent(ProfilerCompilationCanary.class)); + } + + @Override + protected String getTestDescription() { + return "Test UI for verifying that Profiler.isEnabled() causes no side effects in generated javascript"; + } +} diff --git a/uitest/src/com/vaadin/tests/debug/ProfilerZeroOverheadTest.java b/uitest/src/com/vaadin/tests/debug/ProfilerZeroOverheadTest.java new file mode 100644 index 0000000000..659823e49a --- /dev/null +++ b/uitest/src/com/vaadin/tests/debug/ProfilerZeroOverheadTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.debug; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.By; + +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class ProfilerZeroOverheadTest extends SingleBrowserTest { + @Test + public void testZeroOverhead() { + openTestURL(); + + /* + * This will get the compiled JS for the + * ProfilerCompilationCanary.canaryWithProfiler method. Expected to be + * something like "function canaryWithProfiler(){\n}" with a PRETTY + * non-draft widgetset. + */ + String canaryMethodString = findElement(By.className("gwt-Label")) + .getText(); + + // Only look at the method body to avoid false negatives if e.g. + // obfuscation changes + int bodyStart = canaryMethodString.indexOf('{'); + int bodyEnd = canaryMethodString.lastIndexOf('}'); + + String methodBody = canaryMethodString + .substring(bodyStart + 1, bodyEnd); + + // Method body shouldn't contain anything else than whitespace + if (!methodBody.replaceAll("\\s", "").isEmpty()) { + Assert.fail("Canary method is not empty: " + canaryMethodString); + } + } +} diff --git a/uitest/src/com/vaadin/tests/fieldgroup/BasicPersonFormTest.java b/uitest/src/com/vaadin/tests/fieldgroup/BasicPersonFormTest.java index f611325719..2562412480 100644 --- a/uitest/src/com/vaadin/tests/fieldgroup/BasicPersonFormTest.java +++ b/uitest/src/com/vaadin/tests/fieldgroup/BasicPersonFormTest.java @@ -20,18 +20,25 @@ import org.junit.Assert; import com.vaadin.testbench.TestBenchElement; import com.vaadin.testbench.elements.ButtonElement; import com.vaadin.testbench.elements.CheckBoxElement; -import com.vaadin.testbench.elements.NotificationElement; import com.vaadin.testbench.elements.TableElement; import com.vaadin.testbench.elements.TableRowElement; import com.vaadin.testbench.elements.TextAreaElement; import com.vaadin.testbench.elements.TextFieldElement; import com.vaadin.tests.data.bean.Sex; import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.tests.tb3.newelements.FixedNotificationElement; public abstract class BasicPersonFormTest extends MultiBrowserTest { private static final String BEAN_VALUES = "Person [firstName=John, lastName=Doe, email=john@doe.com, age=64, sex=Male, address=Address [streetAddress=John street, postalCode=11223, city=John's town, country=USA], deceased=false, salary=null, salaryDouble=null, rent=null]"; - private int logCounter = 0; + private int logCounter; + + @Override + public void setup() throws Exception { + super.setup(); + + logCounter = 0; + } @Override protected Class<?> getUIClass() { @@ -75,7 +82,7 @@ public abstract class BasicPersonFormTest extends MultiBrowserTest { } protected void closeNotification() { - $(NotificationElement.class).first().close(); + $(FixedNotificationElement.class).first().close(); } protected CheckBoxElement getPostCommitFailsCheckBox() { @@ -169,5 +176,4 @@ public abstract class BasicPersonFormTest extends MultiBrowserTest { assertSelectedSex(Sex.MALE); assertDeceasedValue("NAAAAAH"); } - } diff --git a/uitest/src/com/vaadin/tests/integration/JSR286Portlet.java b/uitest/src/com/vaadin/tests/integration/JSR286Portlet.java index 37f8e21c50..800a056041 100644 --- a/uitest/src/com/vaadin/tests/integration/JSR286Portlet.java +++ b/uitest/src/com/vaadin/tests/integration/JSR286Portlet.java @@ -110,10 +110,16 @@ public class JSR286Portlet extends UI { } private void possiblyChangedModeOrState() { - VaadinPortletRequest request = (VaadinPortletRequest) VaadinPortletService - .getCurrentRequest(); - - userAgent.setValue(getPage().getWebBrowser().getBrowserApplication()); + VaadinPortletRequest request = VaadinPortletService.getCurrentRequest(); + + String censoredUserAgent = getPage().getWebBrowser() + .getBrowserApplication(); + if (censoredUserAgent != null && censoredUserAgent.contains("Chrome/")) { + // Censor version info as it tends to change + censoredUserAgent = censoredUserAgent.replaceAll("Chrome/[^ ]* ", + "Chrome/xyz "); + } + userAgent.setValue(censoredUserAgent); screenWidth.setValue(String.valueOf(getPage().getBrowserWindowWidth())); screenHeight.setValue(String .valueOf(getPage().getBrowserWindowHeight())); diff --git a/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java b/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java index b404747c80..a060d5a57a 100644 --- a/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java +++ b/uitest/src/com/vaadin/tests/push/BasicPushLongPollingTest.java @@ -15,5 +15,19 @@ */ package com.vaadin.tests.push; +import org.junit.Test; + public class BasicPushLongPollingTest extends BasicPushTest { + + @Test + public void pushAfterServerTimeout() throws InterruptedException { + getDriver().get( + getTestUrl().replace("/run/", "/run-push-timeout/") + + "?debug=push"); + sleep(11000); // Wait for server timeout (10s) + + getServerCounterStartButton().click(); + waitUntilServerCounterChanges(); + } + } diff --git a/uitest/src/com/vaadin/tests/push/BasicPushTest.java b/uitest/src/com/vaadin/tests/push/BasicPushTest.java index 0e86902b50..157e3f74ae 100644 --- a/uitest/src/com/vaadin/tests/push/BasicPushTest.java +++ b/uitest/src/com/vaadin/tests/push/BasicPushTest.java @@ -58,11 +58,11 @@ public abstract class BasicPushTest extends MultiBrowserTest { return Integer.parseInt(clientCounterElem.getText()); } - private WebElement getIncrementButton() { + protected WebElement getIncrementButton() { return getIncrementButton(this); } - private WebElement getServerCounterStartButton() { + protected WebElement getServerCounterStartButton() { return getServerCounterStartButton(this); } @@ -84,7 +84,7 @@ public abstract class BasicPushTest extends MultiBrowserTest { return t.findElement(By.id(BasicPush.INCREMENT_BUTTON_ID)); } - private void waitUntilClientCounterChanges(final int expectedValue) { + protected void waitUntilClientCounterChanges(final int expectedValue) { waitUntil(new ExpectedCondition<Boolean>() { @Override @@ -94,7 +94,7 @@ public abstract class BasicPushTest extends MultiBrowserTest { }, 10); } - private void waitUntilServerCounterChanges() { + protected void waitUntilServerCounterChanges() { final int counter = BasicPushTest.getServerCounter(this); waitUntil(new ExpectedCondition<Boolean>() { diff --git a/uitest/src/com/vaadin/tests/push/ManualLongPollingPushUI.java b/uitest/src/com/vaadin/tests/push/ManualLongPollingPushUI.java new file mode 100644 index 0000000000..190f6daa24 --- /dev/null +++ b/uitest/src/com/vaadin/tests/push/ManualLongPollingPushUI.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.push; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.vaadin.annotations.Push; +import com.vaadin.server.VaadinRequest; +import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.ui.Transport; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; + +@Push(value = PushMode.MANUAL, transport = Transport.LONG_POLLING) +public class ManualLongPollingPushUI extends AbstractTestUIWithLog { + + ExecutorService executor = Executors.newFixedThreadPool(1); + + @Override + protected void setup(VaadinRequest request) { + Button b = new Button("Manual push after 1s", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + executor.submit(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + access(new Runnable() { + + @Override + public void run() { + log("Logged after 1s, followed by manual push"); + push(); + } + }); + + } + }); + } + }); + addComponent(b); + + b = new Button("Double manual push after 1s", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + executor.submit(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + access(new Runnable() { + + @Override + public void run() { + log("First message logged after 1s, followed by manual push"); + push(); + log("Second message logged after 1s, followed by manual push"); + push(); + } + }); + + } + }); + } + }); + addComponent(b); + + } + +} diff --git a/uitest/src/com/vaadin/tests/push/ManualLongPollingPushUITest.java b/uitest/src/com/vaadin/tests/push/ManualLongPollingPushUITest.java new file mode 100644 index 0000000000..096204ff75 --- /dev/null +++ b/uitest/src/com/vaadin/tests/push/ManualLongPollingPushUITest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.push; + +import org.junit.Test; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.support.ui.ExpectedCondition; + +import com.vaadin.testbench.elements.ButtonElement; +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class ManualLongPollingPushUITest extends SingleBrowserTest { + + @Test + public void doubleManualPushDoesNotFreezeApplication() { + openTestURL(); + $(ButtonElement.class).caption("Double manual push after 1s").first() + .click(); + waitUntilLogText("2. Second message logged after 1s, followed by manual push"); + $(ButtonElement.class).caption("Manual push after 1s").first().click(); + waitUntilLogText("3. Logged after 1s, followed by manual push"); + } + + private void waitUntilLogText(final String expected) { + waitUntil(new ExpectedCondition<Boolean>() { + private String actual; + + @Override + public Boolean apply(WebDriver arg0) { + actual = getLogRow(0); + return expected.equals(actual); + } + + @Override + public String toString() { + return String.format("log text to become '%s' (was: '%s')", + expected, actual); + } + }); + } +} diff --git a/uitest/src/com/vaadin/tests/resources/SpecialCharsInThemeResources.java b/uitest/src/com/vaadin/tests/resources/SpecialCharsInThemeResources.java new file mode 100644 index 0000000000..e584ec73cc --- /dev/null +++ b/uitest/src/com/vaadin/tests/resources/SpecialCharsInThemeResources.java @@ -0,0 +1,52 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.resources; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.tests.tb3.SingleBrowserTest; + +public class SpecialCharsInThemeResources extends SingleBrowserTest { + + @Test + public void loadThemeResource() { + loadResource("/VAADIN/themes/tests-tickets/ordinary.txt"); + checkSource(); + } + + @Test + public void loadThemeResourceWithPercentage() { + loadResource("/VAADIN/themes/tests-tickets/percentagein%2520name.txt"); + checkSource(); + } + + @Test + public void loadThemeResourceWithSpecialChars() { + loadResource("/VAADIN/themes/tests-tickets/folder%20with%20space/resource%20with%20special%20$chars@.txt"); + checkSource(); + } + + private void loadResource(String path) { + getDriver().get(getBaseURL() + path); + } + + private void checkSource() { + String source = getDriver().getPageSource(); + Assert.assertTrue("Incorrect contents (was: " + source + ")", + source.contains("Just ordinary contents here")); + } +} diff --git a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java index 665f1668b2..a58575890e 100644 --- a/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java +++ b/uitest/src/com/vaadin/tests/tb3/AbstractTB3Test.java @@ -51,6 +51,7 @@ import org.openqa.selenium.interactions.Keyboard; import org.openqa.selenium.interactions.Mouse; import org.openqa.selenium.interactions.internal.Coordinates; import org.openqa.selenium.internal.Locatable; +import org.openqa.selenium.internal.WrapsElement; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.RemoteWebDriver; @@ -384,7 +385,7 @@ public abstract class AbstractTB3Test extends ParallelTest { * @return */ public WebElement vaadinElementById(String id) { - return driver.findElement(vaadinLocatorById(id)); + return driver.findElement(By.id(id)); } /** @@ -1118,4 +1119,93 @@ public abstract class AbstractTB3Test extends ParallelTest { return isElementPresent(By.className("v-debugwindow")); } + protected void assertNoHorizontalScrollbar(WebElement element, + String errorMessage) { + // IE rounds clientWidth/clientHeight down and scrollHeight/scrollWidth + // up, so using clientWidth/clientHeight will fail if the element height + // is not an integer + int clientWidth = getClientWidth(element); + int scrollWidth = getScrollWidth(element); + boolean hasScrollbar = scrollWidth > clientWidth; + + Assert.assertFalse( + "The element should not have a horizontal scrollbar (scrollWidth: " + + scrollWidth + ", clientWidth: " + clientWidth + "): " + + errorMessage, hasScrollbar); + } + + protected void assertNoVerticalScrollbar(WebElement element, + String errorMessage) { + // IE rounds clientWidth/clientHeight down and scrollHeight/scrollWidth + // up, so using clientWidth/clientHeight will fail if the element height + // is not an integer + int clientHeight = getClientHeight(element); + int scrollHeight = getScrollHeight(element); + boolean hasScrollbar = scrollHeight > clientHeight; + + Assert.assertFalse( + "The element should not have a vertical scrollbar (scrollHeight: " + + scrollHeight + ", clientHeight: " + clientHeight + + "): " + errorMessage, hasScrollbar); + } + + protected int getScrollHeight(WebElement element) { + return ((Number) executeScript("return arguments[0].scrollHeight;", + element)).intValue(); + } + + protected int getScrollWidth(WebElement element) { + return ((Number) executeScript("return arguments[0].scrollWidth;", + element)).intValue(); + } + + /** + * Returns client height rounded up instead of as double because of IE9 + * issues: https://dev.vaadin.com/ticket/18469 + */ + protected int getClientHeight(WebElement e) { + String script; + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + script = "return arguments[0].clientHeight;"; // + } else { + script = "var cs = window.getComputedStyle(arguments[0]);" + + "return Math.ceil(parseFloat(cs.height)+parseFloat(cs.paddingTop)+parseFloat(cs.paddingBottom));"; + } + return ((Number) executeScript(script, e)).intValue(); + } + + /** + * Returns client width rounded up instead of as double because of IE9 + * issues: https://dev.vaadin.com/ticket/18469 + */ + protected int getClientWidth(WebElement e) { + String script; + if (BrowserUtil.isIE8(getDesiredCapabilities())) { + script = "return arguments[0].clientWidth;"; + } else { + script = "var cs = window.getComputedStyle(arguments[0]);" + + "var h = parseFloat(cs.width)+parseFloat(cs.paddingLeft)+parseFloat(cs.paddingRight);" + + "return Math.ceil(h);"; + } + + return ((Number) executeScript(script, e)).intValue(); + } + + protected void assertElementsEquals(WebElement expectedElement, + WebElement actualElement) { + while (expectedElement instanceof WrapsElement) { + expectedElement = ((WrapsElement) expectedElement) + .getWrappedElement(); + } + while (actualElement instanceof WrapsElement) { + actualElement = ((WrapsElement) actualElement).getWrappedElement(); + } + + Assert.assertEquals(expectedElement, actualElement); + } + + protected WebElement getActiveElement() { + return (WebElement) executeScript("return document.activeElement;"); + + } } diff --git a/uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java b/uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java index 16df2ace74..23ead80fce 100644 --- a/uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java +++ b/uitest/src/com/vaadin/tests/tb3/MultiBrowserTest.java @@ -84,6 +84,8 @@ public abstract class MultiBrowserTest extends PrivateTB3Configuration { @Override public void setDesiredCapabilities(DesiredCapabilities desiredCapabilities) { + super.setDesiredCapabilities(desiredCapabilities); + if (BrowserUtil.isIE(desiredCapabilities)) { if (requireWindowFocusForIE()) { desiredCapabilities.setCapability( @@ -103,8 +105,6 @@ public abstract class MultiBrowserTest extends PrivateTB3Configuration { "name", String.format("%s.%s", getClass().getCanonicalName(), testName.getMethodName())); - - super.setDesiredCapabilities(desiredCapabilities); } @Override diff --git a/uitest/src/com/vaadin/tests/tb3/newelements/WindowElement.java b/uitest/src/com/vaadin/tests/tb3/newelements/WindowElement.java index 34344324d0..784d203ab0 100644 --- a/uitest/src/com/vaadin/tests/tb3/newelements/WindowElement.java +++ b/uitest/src/com/vaadin/tests/tb3/newelements/WindowElement.java @@ -14,6 +14,7 @@ public class WindowElement extends com.vaadin.testbench.elements.WindowElement { private final String restoreBoxClass = "v-window-restorebox"; private final String maximizeBoxClass = "v-window-maximizebox"; + private final String closeBoxClass = "v-window-closebox"; public void restore() { if (isMaximized()) { @@ -63,4 +64,13 @@ public class WindowElement extends com.vaadin.testbench.elements.WindowElement { public String getCaption() { return findElement(By.className("v-window-header")).getText(); } + + private WebElement getCloseButton() { + return findElement(By.className(closeBoxClass)); + } + + public void close() { + getCloseButton().click(); + + } } diff --git a/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java index 4582123f5f..0a57b77aa3 100644 --- a/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java +++ b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChange.java @@ -29,6 +29,7 @@ import com.vaadin.ui.ComboBox; import com.vaadin.ui.Embedded; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.MenuBar; +import com.vaadin.ui.MenuBar.MenuItem; import com.vaadin.ui.Table; import com.vaadin.ui.VerticalLayout; @@ -60,7 +61,10 @@ public class LegacyComponentThemeChange extends AbstractTestUIWithLog { ThemeResource varyingIcon = new ThemeResource("menubar-theme-icon.png"); MenuBar bar = new MenuBar(); bar.addItem("runo", alwaysTheSameIconImage, null); - bar.addItem("seletedtheme", varyingIcon, null); + bar.addItem("selectedtheme", varyingIcon, null); + MenuItem sub = bar.addItem("sub menu", null); + sub.addItem("runo", alwaysTheSameIconImage, null); + sub.addItem("selectedtheme", varyingIcon, null); vl.addComponent(bar); diff --git a/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java index c6593104da..3c992f3af5 100644 --- a/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java +++ b/uitest/src/com/vaadin/tests/themes/LegacyComponentThemeChangeTest.java @@ -122,8 +122,22 @@ public class LegacyComponentThemeChangeTest extends MultiBrowserTest { // The other image should change with the theme WebElement themeImage = $(MenuBarElement.class).first().findElement( - By.xpath(".//span[text()='seletedtheme']/img")); + By.xpath(".//span[text()='selectedtheme']/img")); assertAttributePrefix(themeImage, "src", theme); + + WebElement subMenuItem = $(MenuBarElement.class).first().findElement( + By.xpath(".//span[text()='sub menu']")); + subMenuItem.click(); + + WebElement subMenu = findElement(By.className("v-menubar-popup")); + WebElement subMenuRuno = subMenu.findElement(By + .xpath(".//span[text()='runo']/img")); + String subMenuRunoImageSrc = subMenuRuno.getAttribute("src"); + Assert.assertEquals(getThemeURL("runo") + "icons/16/ok.png", + subMenuRunoImageSrc); + WebElement subMenuThemeImage = subMenu.findElement(By + .xpath(".//span[text()='selectedtheme']/img")); + assertAttributePrefix(subMenuThemeImage, "src", theme); } private void assertAttributePrefix(WebElement element, String attribute, diff --git a/uitest/src/com/vaadin/tests/themes/valo/Accordions.java b/uitest/src/com/vaadin/tests/themes/valo/Accordions.java index c32be01d8d..3c7ad2ad33 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Accordions.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Accordions.java @@ -21,13 +21,14 @@ import com.vaadin.ui.Accordion; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class Accordions extends VerticalLayout implements View { public Accordions() { setMargin(true); Label h1 = new Label("Accordions"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); @@ -38,7 +39,7 @@ public class Accordions extends VerticalLayout implements View { row.addComponent(getAccordion("Normal")); Accordion ac = getAccordion("Borderless"); - ac.addStyleName("borderless"); + ac.addStyleName(ValoTheme.ACCORDION_BORDERLESS); row.addComponent(ac); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/AlignTopIconInButton.java b/uitest/src/com/vaadin/tests/themes/valo/AlignTopIconInButton.java index dc257fb3ec..eb4be8e0d8 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/AlignTopIconInButton.java +++ b/uitest/src/com/vaadin/tests/themes/valo/AlignTopIconInButton.java @@ -20,6 +20,7 @@ import com.vaadin.server.ThemeResource; import com.vaadin.server.VaadinRequest; import com.vaadin.tests.components.AbstractTestUI; import com.vaadin.ui.Button; +import com.vaadin.ui.themes.ValoTheme; /** * Test UI for image icon in button with 'icon-align-top' style. @@ -34,7 +35,7 @@ public class AlignTopIconInButton extends AbstractTestUI { Button button = new Button(); button.setIcon(new ThemeResource("../runo/icons/16/document.png")); addComponent(button); - button.addStyleName("icon-align-top"); + button.addStyleName(ValoTheme.BUTTON_ICON_ALIGN_TOP); button.setCaption("caption"); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/ButtonsAndLinks.java b/uitest/src/com/vaadin/tests/themes/valo/ButtonsAndLinks.java index 9ed48896eb..ee88595ba7 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/ButtonsAndLinks.java +++ b/uitest/src/com/vaadin/tests/themes/valo/ButtonsAndLinks.java @@ -25,6 +25,7 @@ import com.vaadin.ui.Label; import com.vaadin.ui.Link; import com.vaadin.ui.NativeButton; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; /** * @@ -39,11 +40,11 @@ public class ButtonsAndLinks extends VerticalLayout implements View { setMargin(true); Label h1 = new Label("Buttons"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -55,30 +56,30 @@ public class ButtonsAndLinks extends VerticalLayout implements View { row.addComponent(button); button = new Button("Primary"); - button.addStyleName("primary"); + button.addStyleName(ValoTheme.BUTTON_PRIMARY); row.addComponent(button); button = new Button("Friendly"); - button.addStyleName("friendly"); + button.addStyleName(ValoTheme.BUTTON_FRIENDLY); row.addComponent(button); button = new Button("Danger"); - button.addStyleName("danger"); + button.addStyleName(ValoTheme.BUTTON_DANGER); row.addComponent(button); TestIcon testIcon = new TestIcon(10); button = new Button("Small"); - button.addStyleName("small"); + button.addStyleName(ValoTheme.BUTTON_SMALL); button.setIcon(testIcon.get()); row.addComponent(button); button = new Button("Large"); - button.addStyleName("large"); + button.addStyleName(ValoTheme.BUTTON_LARGE); button.setIcon(testIcon.get()); row.addComponent(button); button = new Button("Top"); - button.addStyleName("icon-align-top"); + button.addStyleName(ValoTheme.BUTTON_ICON_ALIGN_TOP); button.setIcon(testIcon.get()); row.addComponent(button); @@ -87,7 +88,7 @@ public class ButtonsAndLinks extends VerticalLayout implements View { row.addComponent(button); button = new Button("Image icon"); - button.addStyleName("icon-align-right"); + button.addStyleName(ValoTheme.BUTTON_ICON_ALIGN_RIGHT); button.setIcon(testIcon.get(true)); row.addComponent(button); @@ -97,36 +98,36 @@ public class ButtonsAndLinks extends VerticalLayout implements View { button = new Button(); button.setIcon(testIcon.get()); - button.addStyleName("icon-only"); + button.addStyleName(ValoTheme.BUTTON_ICON_ONLY); row.addComponent(button); button = new Button("Borderless"); button.setIcon(testIcon.get()); - button.addStyleName("borderless"); + button.addStyleName(ValoTheme.BUTTON_BORDERLESS); row.addComponent(button); button = new Button("Borderless, colored"); button.setIcon(testIcon.get()); - button.addStyleName("borderless-colored"); + button.addStyleName(ValoTheme.BUTTON_BORDERLESS_COLORED); row.addComponent(button); button = new Button("Quiet"); button.setIcon(testIcon.get()); - button.addStyleName("quiet"); + button.addStyleName(ValoTheme.BUTTON_QUIET); row.addComponent(button); button = new Button("Link style"); button.setIcon(testIcon.get()); - button.addStyleName("link"); + button.addStyleName(ValoTheme.BUTTON_LINK); row.addComponent(button); button = new Button("Icon on right"); button.setIcon(testIcon.get()); - button.addStyleName("icon-align-right"); + button.addStyleName(ValoTheme.BUTTON_ICON_ALIGN_RIGHT); row.addComponent(button); CssLayout group = new CssLayout(); - group.addStyleName("v-component-group"); + group.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP); row.addComponent(group); button = new Button("One"); @@ -137,22 +138,22 @@ public class ButtonsAndLinks extends VerticalLayout implements View { group.addComponent(button); button = new Button("Tiny"); - button.addStyleName("tiny"); + button.addStyleName(ValoTheme.BUTTON_TINY); row.addComponent(button); button = new Button("Huge"); - button.addStyleName("huge"); + button.addStyleName(ValoTheme.BUTTON_HUGE); row.addComponent(button); NativeButton nbutton = new NativeButton("Native"); row.addComponent(nbutton); h1 = new Label("Links"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -167,16 +168,16 @@ public class ButtonsAndLinks extends VerticalLayout implements View { row.addComponent(link); link = new Link("Small", new ExternalResource("https://vaadin.com")); - link.addStyleName("small"); + link.addStyleName(ValoTheme.LINK_SMALL); row.addComponent(link); link = new Link("Large", new ExternalResource("https://vaadin.com")); - link.addStyleName("large"); + link.addStyleName(ValoTheme.LINK_LARGE); row.addComponent(link); link = new Link(null, new ExternalResource("https://vaadin.com")); link.setIcon(testIcon.get()); - link.addStyleName("large"); + link.addStyleName(ValoTheme.LINK_LARGE); row.addComponent(link); link = new Link("Disabled", new ExternalResource("https://vaadin.com")); diff --git a/uitest/src/com/vaadin/tests/themes/valo/CalendarTest.java b/uitest/src/com/vaadin/tests/themes/valo/CalendarTest.java index 280ddf98b7..e18665f2fa 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/CalendarTest.java +++ b/uitest/src/com/vaadin/tests/themes/valo/CalendarTest.java @@ -286,7 +286,7 @@ public class CalendarTest extends GridLayout implements View { hl.addComponent(captionLabel); CssLayout group = new CssLayout(); - group.addStyleName("v-component-group"); + group.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP); group.addComponent(dayButton); group.addComponent(weekButton); group.addComponent(monthButton); @@ -922,7 +922,7 @@ public class CalendarTest extends GridLayout implements View { scheduleEventPopup.setModal(true); scheduleEventPopup.center(); - scheduleEventFieldLayout.addStyleName("light"); + scheduleEventFieldLayout.addStyleName(ValoTheme.FORMLAYOUT_LIGHT); scheduleEventFieldLayout.setMargin(false); layout.addComponent(scheduleEventFieldLayout); @@ -939,7 +939,7 @@ public class CalendarTest extends GridLayout implements View { } } }); - applyEventButton.addStyleName("primary"); + applyEventButton.addStyleName(ValoTheme.BUTTON_PRIMARY); Button cancel = new Button("Cancel", new ClickListener() { private static final long serialVersionUID = 1L; @@ -958,7 +958,7 @@ public class CalendarTest extends GridLayout implements View { deleteCalendarEvent(); } }); - deleteEventButton.addStyleName("borderless"); + deleteEventButton.addStyleName(ValoTheme.BUTTON_BORDERLESS); scheduleEventPopup.addCloseListener(new Window.CloseListener() { private static final long serialVersionUID = 1L; @@ -970,7 +970,7 @@ public class CalendarTest extends GridLayout implements View { }); HorizontalLayout buttons = new HorizontalLayout(); - buttons.addStyleName("v-window-bottom-toolbar"); + buttons.addStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR); buttons.setWidth("100%"); buttons.setSpacing(true); buttons.addComponent(deleteEventButton); diff --git a/uitest/src/com/vaadin/tests/themes/valo/CheckBoxes.java b/uitest/src/com/vaadin/tests/themes/valo/CheckBoxes.java index 9a889b3bda..f77cf9a315 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/CheckBoxes.java +++ b/uitest/src/com/vaadin/tests/themes/valo/CheckBoxes.java @@ -23,17 +23,18 @@ import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.OptionGroup; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class CheckBoxes extends VerticalLayout implements View { public CheckBoxes() { setMargin(true); Label h1 = new Label("Check Boxes"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -72,11 +73,11 @@ public class CheckBoxes extends VerticalLayout implements View { row.addComponent(check); check = new CheckBox("Small", true); - check.addStyleName("small"); + check.addStyleName(ValoTheme.CHECKBOX_SMALL); row.addComponent(check); check = new CheckBox("Large", true); - check.addStyleName("large"); + check.addStyleName(ValoTheme.CHECKBOX_LARGE); row.addComponent(check); check = new CheckBox("Disabled", true); @@ -90,11 +91,11 @@ public class CheckBoxes extends VerticalLayout implements View { row.addComponent(check); h1 = new Label("Option Groups"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -124,7 +125,7 @@ public class CheckBoxes extends VerticalLayout implements View { row.addComponent(options); options = new OptionGroup("Choose one, small"); - options.addStyleName("small"); + options.addStyleName(ValoTheme.OPTIONGROUP_SMALL); options.setMultiSelect(false); options.addItem("Option One"); options.addItem("Option Two"); @@ -136,7 +137,7 @@ public class CheckBoxes extends VerticalLayout implements View { row.addComponent(options); options = new OptionGroup("Choose many, small"); - options.addStyleName("small"); + options.addStyleName(ValoTheme.OPTIONGROUP_SMALL); options.setMultiSelect(true); options.addItem("Option One"); options.addItem("Option Two"); @@ -148,7 +149,7 @@ public class CheckBoxes extends VerticalLayout implements View { row.addComponent(options); options = new OptionGroup("Choose one, large"); - options.addStyleName("large"); + options.addStyleName(ValoTheme.OPTIONGROUP_LARGE); options.setMultiSelect(false); options.addItem("Option One"); options.addItem("Option Two"); @@ -160,7 +161,7 @@ public class CheckBoxes extends VerticalLayout implements View { row.addComponent(options); options = new OptionGroup("Choose many, large"); - options.addStyleName("large"); + options.addStyleName(ValoTheme.OPTIONGROUP_LARGE); options.setMultiSelect(true); options.addItem("Option One"); options.addItem("Option Two"); @@ -172,7 +173,7 @@ public class CheckBoxes extends VerticalLayout implements View { row.addComponent(options); options = new OptionGroup("Horizontal items"); - options.addStyleName("horizontal"); + options.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL); options.addItem("Option One"); two = options.addItem("Option Two, with a longer caption"); options.addItem("Option Three"); @@ -185,7 +186,7 @@ public class CheckBoxes extends VerticalLayout implements View { options = new OptionGroup("Horizontal items, explicit width"); options.setMultiSelect(true); options.setWidth("500px"); - options.addStyleName("horizontal"); + options.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL); options.addItem("Option One"); two = options.addItem("Option Two, with a longer caption"); options.addItem("Option Three"); diff --git a/uitest/src/com/vaadin/tests/themes/valo/ColorPickers.java b/uitest/src/com/vaadin/tests/themes/valo/ColorPickers.java index a7fd60ea51..8e32b07ebd 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/ColorPickers.java +++ b/uitest/src/com/vaadin/tests/themes/valo/ColorPickers.java @@ -23,17 +23,18 @@ import com.vaadin.ui.ColorPicker; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class ColorPickers extends VerticalLayout implements View { public ColorPickers() { setMargin(true); Label h1 = new Label("Color Pickers"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); diff --git a/uitest/src/com/vaadin/tests/themes/valo/ComboBoxes.java b/uitest/src/com/vaadin/tests/themes/valo/ComboBoxes.java index 98a9cad83c..4a88d87cd2 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/ComboBoxes.java +++ b/uitest/src/com/vaadin/tests/themes/valo/ComboBoxes.java @@ -25,17 +25,18 @@ import com.vaadin.ui.CssLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class ComboBoxes extends VerticalLayout implements View { public ComboBoxes() { setMargin(true); Label h1 = new Label("Combo Boxes"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -52,7 +53,7 @@ public class ComboBoxes extends VerticalLayout implements View { CssLayout group = new CssLayout(); group.setCaption("Grouped with a Button"); - group.addStyleName("v-component-group"); + group.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP); row.addComponent(group); combo = new ComboBox(); @@ -104,7 +105,7 @@ public class ComboBoxes extends VerticalLayout implements View { combo.setNullSelectionAllowed(false); combo.select("Option One"); combo.setComponentError(new UserError("Fix it, now!")); - combo.addStyleName("borderless"); + combo.addStyleName(ValoTheme.COMBOBOX_BORDERLESS); row.addComponent(combo); combo = new ComboBox("Disabled"); @@ -144,7 +145,7 @@ public class ComboBoxes extends VerticalLayout implements View { combo.setContainerDataSource(ValoThemeUI.generateContainer(200, false)); combo.setItemCaptionPropertyId(ValoThemeUI.CAPTION_PROPERTY); combo.setItemIconPropertyId(ValoThemeUI.ICON_PROPERTY); - combo.addStyleName("small"); + combo.addStyleName(ValoTheme.COMBOBOX_SMALL); row.addComponent(combo); combo = new ComboBox("Large"); @@ -152,7 +153,7 @@ public class ComboBoxes extends VerticalLayout implements View { combo.setContainerDataSource(ValoThemeUI.generateContainer(200, false)); combo.setItemCaptionPropertyId(ValoThemeUI.CAPTION_PROPERTY); combo.setItemIconPropertyId(ValoThemeUI.ICON_PROPERTY); - combo.addStyleName("large"); + combo.addStyleName(ValoTheme.COMBOBOX_LARGE); row.addComponent(combo); combo = new ComboBox("Borderless"); @@ -160,7 +161,7 @@ public class ComboBoxes extends VerticalLayout implements View { combo.addItem("Option One"); combo.addItem("Option Two"); combo.addItem("Option Three"); - combo.addStyleName("borderless"); + combo.addStyleName(ValoTheme.COMBOBOX_BORDERLESS); row.addComponent(combo); combo = new ComboBox("Tiny"); @@ -168,7 +169,7 @@ public class ComboBoxes extends VerticalLayout implements View { combo.setContainerDataSource(ValoThemeUI.generateContainer(200, false)); combo.setItemCaptionPropertyId(ValoThemeUI.CAPTION_PROPERTY); combo.setItemIconPropertyId(ValoThemeUI.ICON_PROPERTY); - combo.addStyleName("tiny"); + combo.addStyleName(ValoTheme.COMBOBOX_TINY); row.addComponent(combo); combo = new ComboBox("Huge"); @@ -176,7 +177,7 @@ public class ComboBoxes extends VerticalLayout implements View { combo.setContainerDataSource(ValoThemeUI.generateContainer(200, false)); combo.setItemCaptionPropertyId(ValoThemeUI.CAPTION_PROPERTY); combo.setItemIconPropertyId(ValoThemeUI.ICON_PROPERTY); - combo.addStyleName("huge"); + combo.addStyleName(ValoTheme.COMBOBOX_HUGE); row.addComponent(combo); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/CommonParts.java b/uitest/src/com/vaadin/tests/themes/valo/CommonParts.java index 52cc43ac28..ea7c42ba2e 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/CommonParts.java +++ b/uitest/src/com/vaadin/tests/themes/valo/CommonParts.java @@ -51,13 +51,14 @@ import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; import com.vaadin.ui.Window.CloseEvent; import com.vaadin.ui.Window.CloseListener; +import com.vaadin.ui.themes.ValoTheme; public class CommonParts extends VerticalLayout implements View { public CommonParts() { setMargin(true); Label h1 = new Label("Common UI Elements"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); GridLayout row = new GridLayout(2, 3); @@ -83,7 +84,7 @@ public class CommonParts extends VerticalLayout implements View { CssLayout group = new CssLayout(); group.setCaption("Show the loading indicator for…"); - group.addStyleName("v-component-group"); + group.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP); content.addComponent(group); Button loading = new Button("0.8"); loading.addClickListener(new ClickListener() { @@ -127,13 +128,13 @@ public class CommonParts extends VerticalLayout implements View { Label spinnerDesc = new Label( "The theme also provides a mixin that you can use to include a spinner anywhere in your application. Below is a Label with a custom style name, for which the spinner mixin is added."); - spinnerDesc.addStyleName("small"); + spinnerDesc.addStyleName(ValoTheme.LABEL_SMALL); spinnerDesc.setCaption("Spinner"); content.addComponent(spinnerDesc); if (!ValoThemeUI.isTestMode()) { Label spinner = new Label(); - spinner.addStyleName("spinner"); + spinner.addStyleName(ValoTheme.LABEL_SPINNER); content.addComponent(spinner); } @@ -172,7 +173,7 @@ public class CommonParts extends VerticalLayout implements View { addComponent(title); description.setInputPrompt("Description for the notification"); - description.addStyleName("small"); + description.addStyleName(ValoTheme.TEXTAREA_SMALL); description.addValueChangeListener(new ValueChangeListener() { @Override public void valueChange(ValueChangeEvent event) { @@ -217,7 +218,7 @@ public class CommonParts extends VerticalLayout implements View { type.addItem("Error", typeCommand).setCheckable(true); type.addItem("System", typeCommand).setCheckable(true); addComponent(type); - type.addStyleName("small"); + type.addStyleName(ValoTheme.MENUBAR_SMALL); Command styleCommand = new Command() { @Override @@ -249,16 +250,16 @@ public class CommonParts extends VerticalLayout implements View { style.addItem("Small", styleCommand).setCheckable(true); style.addItem("Closable", styleCommand).setCheckable(true); addComponent(style); - style.addStyleName("small"); + style.addStyleName(ValoTheme.MENUBAR_SMALL); CssLayout group = new CssLayout(); group.setCaption("Fade delay"); - group.addStyleName("v-component-group"); + group.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP); addComponent(group); delay.setInputPrompt("Infinite"); - delay.addStyleName("align-right"); - delay.addStyleName("small"); + delay.addStyleName(ValoTheme.TEXTFIELD_ALIGN_RIGHT); + delay.addStyleName(ValoTheme.TEXTFIELD_SMALL); delay.setWidth("7em"); delay.addValueChangeListener(new ValueChangeListener() { @Override @@ -284,8 +285,8 @@ public class CommonParts extends VerticalLayout implements View { }); clear.setIcon(FontAwesome.TIMES_CIRCLE); clear.addStyleName("last"); - clear.addStyleName("small"); - clear.addStyleName("icon-only"); + clear.addStyleName(ValoTheme.BUTTON_SMALL); + clear.addStyleName(ValoTheme.BUTTON_ICON_ONLY); group.addComponent(clear); group.addComponent(new Label(" msec", ContentMode.HTML)); @@ -301,7 +302,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -311,7 +312,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -321,7 +322,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -331,7 +332,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -341,7 +342,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -351,7 +352,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -361,7 +362,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -371,7 +372,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); pos = new Button("", new ClickListener() { @@ -381,7 +382,7 @@ public class CommonParts extends VerticalLayout implements View { notification.show(Page.getCurrent()); } }); - pos.addStyleName("small"); + pos.addStyleName(ValoTheme.BUTTON_SMALL); grid.addComponent(pos); } @@ -397,35 +398,35 @@ public class CommonParts extends VerticalLayout implements View { { setSpacing(true); setMargin(true); - addStyleName("wrapping"); + addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); addComponent(new Label( "Try out different tooltips/descriptions by hovering over the labels.")); Label label = new Label("Simple"); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); label.setDescription("Simple tooltip message"); addComponent(label); label = new Label("Long"); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); label.setDescription("Long tooltip message. Inmensae subtilitatis, obscuris et malesuada fames. Salutantibus vitae elit libero, a pharetra augue."); addComponent(label); label = new Label("HTML tooltip"); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); label.setDescription("<div><h1>Ut enim ad minim veniam, quis nostrud exercitation</h1><p><span>Morbi fringilla convallis sapien, id pulvinar odio volutpat.</span> <span>Vivamus sagittis lacus vel augue laoreet rutrum faucibus.</span> <span>Donec sed odio operae, eu vulputate felis rhoncus.</span> <span>At nos hinc posthac, sitientis piros Afros.</span> <span>Tu quoque, Brute, fili mi, nihil timor populi, nihil!</span></p><p><span>Gallia est omnis divisa in partes tres, quarum.</span> <span>Praeterea iter est quasdam res quas ex communi.</span> <span>Cum ceteris in veneratione tui montes, nascetur mus.</span> <span>Quam temere in vitiis, legem sancimus haerentia.</span> <span>Idque Caesaris facere voluntate liceret: sese habere.</span></p></div>"); addComponent(label); label = new Label("With an error message"); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); label.setDescription("Simple tooltip message"); label.setComponentError(new UserError( "Something terrible has happened")); addComponent(label); label = new Label("With a long error message"); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); label.setDescription("Simple tooltip message"); label.setComponentError(new UserError( "<h2>Contra legem facit qui id facit quod lex prohibet <span>Tityre, tu patulae recubans sub tegmine fagi dolor.</span> <span>Tityre, tu patulae recubans sub tegmine fagi dolor.</span> <span>Prima luce, cum quibus mons aliud consensu ab eo.</span> <span>Quid securi etiam tamquam eu fugiat nulla pariatur.</span> <span>Fabio vel iudice vincam, sunt in culpa qui officia.</span> <span>Nihil hic munitissimus habendi senatus locus, nihil horum?</span></p><p><span>Plura mihi bona sunt, inclinet, amari petere vellent.</span> <span>Integer legentibus erat a ante historiarum dapibus.</span> <span>Quam diu etiam furor iste tuus nos eludet?</span> <span>Nec dubitamus multa iter quae et nos invenerat.</span> <span>Quisque ut dolor gravida, placerat libero vel, euismod.</span> <span>Quae vero auctorem tractata ab fiducia dicuntur.</span></h2>", @@ -434,7 +435,7 @@ public class CommonParts extends VerticalLayout implements View { addComponent(label); label = new Label("Error message only"); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); label.setComponentError(new UserError( "Something terrible has happened")); addComponent(label); @@ -479,7 +480,7 @@ public class CommonParts extends VerticalLayout implements View { Alignment.TOP_RIGHT); toolbar = toolbarLayout; } - toolbar.addStyleName("v-window-top-toolbar"); + toolbar.addStyleName(ValoTheme.WINDOW_TOP_TOOLBAR); root.addComponent(toolbar); } @@ -498,7 +499,7 @@ public class CommonParts extends VerticalLayout implements View { "Another"); tabs.addTab(new Label(" ", ContentMode.HTML), "One more"); - tabs.addStyleName("padded-tabbar"); + tabs.addStyleName(ValoTheme.TABSHEET_PADDED_TABBAR); tabs.addSelectedTabChangeListener(new SelectedTabChangeListener() { @Override public void selectedTabChange( @@ -514,9 +515,9 @@ public class CommonParts extends VerticalLayout implements View { } else if (!autoHeight) { Panel p = new Panel(); p.setSizeFull(); - p.addStyleName("borderless"); + p.addStyleName(ValoTheme.PANEL_BORDERLESS); if (!toolbarVisible || !toolbarLayout) { - p.addStyleName("scroll-divider"); + p.addStyleName(ValoTheme.PANEL_SCROLL_INDICATOR); } VerticalLayout l = new VerticalLayout(); l.addComponent(new Label( @@ -538,13 +539,13 @@ public class CommonParts extends VerticalLayout implements View { HorizontalLayout footer = new HorizontalLayout(); footer.setWidth("100%"); footer.setSpacing(true); - footer.addStyleName("v-window-bottom-toolbar"); + footer.addStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR); Label footerText = new Label("Footer text"); footerText.setSizeUndefined(); Button ok = new Button("OK"); - ok.addStyleName("primary"); + ok.addStyleName(ValoTheme.BUTTON_PRIMARY); Button cancel = new Button("Cancel"); @@ -614,7 +615,7 @@ public class CommonParts extends VerticalLayout implements View { if (selectedItem.getText() .equals("Borderless Toolbars")) { - toolbarStyle = selectedItem.isChecked() ? "borderless" + toolbarStyle = selectedItem.isChecked() ? ValoTheme.MENUBAR_BORDERLESS : null; } @@ -630,7 +631,7 @@ public class CommonParts extends VerticalLayout implements View { MenuItem option = options.addItem("Footer", optionsCommand); option.setCheckable(true); option.setChecked(true); - options.addStyleName("small"); + options.addStyleName(ValoTheme.MENUBAR_SMALL); addComponent(options); options = new MenuBar(); @@ -643,7 +644,7 @@ public class CommonParts extends VerticalLayout implements View { .setCheckable(true); options.addItem("Borderless Toolbars", optionsCommand) .setCheckable(true); - options.addStyleName("small"); + options.addStyleName(ValoTheme.MENUBAR_SMALL); addComponent(options); Command optionsCommand2 = new Command() { @@ -671,7 +672,7 @@ public class CommonParts extends VerticalLayout implements View { options.addItem("Resizable", optionsCommand2) .setCheckable(true); options.addItem("Modal", optionsCommand2).setCheckable(true); - options.addStyleName("small"); + options.addStyleName(ValoTheme.MENUBAR_SMALL); addComponent(options); final Button show = new Button("Open Window", @@ -684,7 +685,7 @@ public class CommonParts extends VerticalLayout implements View { event.getButton().setEnabled(false); } }); - show.addStyleName("primary"); + show.addStyleName(ValoTheme.BUTTON_PRIMARY); addComponent(show); final CheckBox hidden = new CheckBox("Hidden"); diff --git a/uitest/src/com/vaadin/tests/themes/valo/DateFields.java b/uitest/src/com/vaadin/tests/themes/valo/DateFields.java index 4b29f83621..9c95b7400c 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/DateFields.java +++ b/uitest/src/com/vaadin/tests/themes/valo/DateFields.java @@ -35,17 +35,18 @@ import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.InlineDateField; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class DateFields extends VerticalLayout implements View { public DateFields() { setMargin(true); Label h1 = new Label("Date Fields"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -61,12 +62,12 @@ public class DateFields extends VerticalLayout implements View { date = new DateField("Error, borderless"); setDate(date); date.setComponentError(new UserError("Fix it, now!")); - date.addStyleName("borderless"); + date.addStyleName(ValoTheme.DATEFIELD_BORDERLESS); row.addComponent(date); CssLayout group = new CssLayout(); group.setCaption("Grouped with a Button"); - group.addStyleName("v-component-group"); + group.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP); row.addComponent(group); final DateField date2 = new DateField(); @@ -143,19 +144,19 @@ public class DateFields extends VerticalLayout implements View { date = new DateField("Small"); setDate(date); date.setResolution(Resolution.DAY); - date.addStyleName("small"); + date.addStyleName(ValoTheme.DATEFIELD_SMALL); row.addComponent(date); date = new DateField("Large"); setDate(date); date.setResolution(Resolution.DAY); - date.addStyleName("large"); + date.addStyleName(ValoTheme.DATEFIELD_LARGE); row.addComponent(date); date = new DateField("Borderless"); setDate(date); date.setResolution(Resolution.DAY); - date.addStyleName("borderless"); + date.addStyleName(ValoTheme.DATEFIELD_BORDERLESS); row.addComponent(date); date = new DateField("Week numbers"); @@ -179,13 +180,13 @@ public class DateFields extends VerticalLayout implements View { date = new DateField("Tiny"); setDate(date); date.setResolution(Resolution.DAY); - date.addStyleName("tiny"); + date.addStyleName(ValoTheme.DATEFIELD_TINY); row.addComponent(date); date = new DateField("Huge"); setDate(date); date.setResolution(Resolution.DAY); - date.addStyleName("huge"); + date.addStyleName(ValoTheme.DATEFIELD_HUGE); row.addComponent(date); date = new InlineDateField("Date picker"); diff --git a/uitest/src/com/vaadin/tests/themes/valo/Dragging.java b/uitest/src/com/vaadin/tests/themes/valo/Dragging.java index 27bdea7d8a..8de518be23 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Dragging.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Dragging.java @@ -46,6 +46,7 @@ import com.vaadin.ui.MenuBar; import com.vaadin.ui.MenuBar.MenuItem; import com.vaadin.ui.Notification; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; /** * @@ -61,7 +62,7 @@ public class Dragging extends VerticalLayout implements View { setSpacing(true); Label h1 = new Label("Dragging Components"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); MenuBar options = new MenuBar(); @@ -74,9 +75,9 @@ public class Dragging extends VerticalLayout implements View { @Override public void menuSelected(MenuItem selectedItem) { if (selectedItem.isChecked()) { - sample.removeStyleName("no-vertical-drag-hints"); + sample.removeStyleName(ValoTheme.DRAG_AND_DROP_WRAPPER_NO_VERTICAL_DRAG_HINTS); } else { - sample.addStyleName("no-vertical-drag-hints"); + sample.addStyleName(ValoTheme.DRAG_AND_DROP_WRAPPER_NO_VERTICAL_DRAG_HINTS); } } }); @@ -87,9 +88,9 @@ public class Dragging extends VerticalLayout implements View { @Override public void menuSelected(MenuItem selectedItem) { if (selectedItem.isChecked()) { - sample.removeStyleName("no-horizontal-drag-hints"); + sample.removeStyleName(ValoTheme.DRAG_AND_DROP_WRAPPER_NO_HORIZONTAL_DRAG_HINTS); } else { - sample.addStyleName("no-horizontal-drag-hints"); + sample.addStyleName(ValoTheme.DRAG_AND_DROP_WRAPPER_NO_HORIZONTAL_DRAG_HINTS); } } }); @@ -100,9 +101,9 @@ public class Dragging extends VerticalLayout implements View { @Override public void menuSelected(MenuItem selectedItem) { if (selectedItem.isChecked()) { - sample.removeStyleName("no-box-drag-hints"); + sample.removeStyleName(ValoTheme.DRAG_AND_DROP_WRAPPER_NO_BOX_DRAG_HINTS); } else { - sample.addStyleName("no-box-drag-hints"); + sample.addStyleName(ValoTheme.DRAG_AND_DROP_WRAPPER_NO_BOX_DRAG_HINTS); } } }); diff --git a/uitest/src/com/vaadin/tests/themes/valo/Forms.java b/uitest/src/com/vaadin/tests/themes/valo/Forms.java index 90a6c51496..91fe473d60 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Forms.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Forms.java @@ -36,6 +36,7 @@ import com.vaadin.ui.RichTextArea; import com.vaadin.ui.TextArea; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; /** * @@ -48,18 +49,18 @@ public class Forms extends VerticalLayout implements View { setMargin(true); Label title = new Label("Forms"); - title.addStyleName("h1"); + title.addStyleName(ValoTheme.LABEL_H1); addComponent(title); final FormLayout form = new FormLayout(); form.setMargin(false); form.setWidth("800px"); - form.addStyleName("light"); + form.addStyleName(ValoTheme.FORMLAYOUT_LIGHT); addComponent(form); Label section = new Label("Personal Info"); - section.addStyleName("h2"); - section.addStyleName("colored"); + section.addStyleName(ValoTheme.LABEL_H2); + section.addStyleName(ValoTheme.LABEL_COLORED); form.addComponent(section); StringGenerator sg = new StringGenerator(); @@ -81,12 +82,12 @@ public class Forms extends VerticalLayout implements View { sex.addItem("Female"); sex.addItem("Male"); sex.select("Male"); - sex.addStyleName("horizontal"); + sex.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL); form.addComponent(sex); section = new Label("Contact Info"); - section.addStyleName("h3"); - section.addStyleName("colored"); + section.addStyleName(ValoTheme.LABEL_H3); + section.addStyleName(ValoTheme.LABEL_COLORED); form.addComponent(section); TextField email = new TextField("Email"); @@ -120,14 +121,14 @@ public class Forms extends VerticalLayout implements View { period.addItem("Montly"); period.setNullSelectionAllowed(false); period.select("Weekly"); - period.addStyleName("small"); + period.addStyleName(ValoTheme.COMBOBOX_SMALL); period.setWidth("10em"); wrap.addComponent(period); form.addComponent(wrap); section = new Label("Additional Info"); - section.addStyleName("h4"); - section.addStyleName("colored"); + section.addStyleName(ValoTheme.LABEL_H4); + section.addStyleName(ValoTheme.LABEL_COLORED); form.addComponent(section); TextField website = new TextField("Website"); @@ -156,15 +157,15 @@ public class Forms extends VerticalLayout implements View { if (readOnly) { bio.setReadOnly(false); form.setReadOnly(false); - form.removeStyleName("light"); + form.removeStyleName(ValoTheme.FORMLAYOUT_LIGHT); event.getButton().setCaption("Save"); - event.getButton().addStyleName("primary"); + event.getButton().addStyleName(ValoTheme.BUTTON_PRIMARY); } else { bio.setReadOnly(true); form.setReadOnly(true); - form.addStyleName("light"); + form.addStyleName(ValoTheme.FORMLAYOUT_LIGHT); event.getButton().setCaption("Edit"); - event.getButton().removeStyleName("primary"); + event.getButton().removeStyleName(ValoTheme.BUTTON_PRIMARY); } } }); @@ -177,7 +178,7 @@ public class Forms extends VerticalLayout implements View { footer.addComponent(edit); Label lastModified = new Label("Last modified by you a minute ago"); - lastModified.addStyleName("light"); + lastModified.addStyleName(ValoTheme.LABEL_LIGHT); footer.addComponent(lastModified); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/Labels.java b/uitest/src/com/vaadin/tests/themes/valo/Labels.java index b5bab3a1d3..9954979d50 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Labels.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Labels.java @@ -23,6 +23,7 @@ import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Panel; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; /** * @@ -34,7 +35,7 @@ public class Labels extends VerticalLayout implements View { setMargin(true); Label h1 = new Label("Labels"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout split = new HorizontalLayout(); @@ -46,16 +47,16 @@ public class Labels extends VerticalLayout implements View { split.addComponent(left); Label huge = new Label("Huge type for display text."); - huge.addStyleName("huge"); + huge.addStyleName(ValoTheme.LABEL_HUGE); left.addComponent(huge); Label large = new Label( "Large type for introductory text. Etiam at risus et justo dignissim congue. Donec congue lacinia dui, a porttitor lectus condimentum laoreet. Nunc eu."); - large.addStyleName("large"); + large.addStyleName(ValoTheme.LABEL_LARGE); left.addComponent(large); Label h2 = new Label("Subtitle"); - h2.addStyleName("h2"); + h2.addStyleName(ValoTheme.LABEL_H2); left.addComponent(h2); Label normal = new Label( @@ -64,20 +65,20 @@ public class Labels extends VerticalLayout implements View { left.addComponent(normal); Label h3 = new Label("Small Title"); - h3.addStyleName("h3"); + h3.addStyleName(ValoTheme.LABEL_H3); left.addComponent(h3); Label small = new Label( "Small type for additional text. Etiam at risus et justo dignissim congue. Donec congue lacinia dui, a porttitor lectus condimentum laoreet. Nunc eu."); - small.addStyleName("small"); + small.addStyleName(ValoTheme.LABEL_SMALL); left.addComponent(small); Label tiny = new Label("Tiny type for minor text."); - tiny.addStyleName("tiny"); + tiny.addStyleName(ValoTheme.LABEL_TINY); left.addComponent(tiny); Label h4 = new Label("Section Title"); - h4.addStyleName("h4"); + h4.addStyleName(ValoTheme.LABEL_H4); left.addComponent(h4); normal = new Label( @@ -94,25 +95,25 @@ public class Labels extends VerticalLayout implements View { Label label = new Label( "Bold type for prominent text. Etiam at risus et justo dignissim congue. Donec congue lacinia dui, a porttitor lectus condimentum laoreet. Nunc eu."); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); right.addComponent(label); label = new Label( "Light type for subtle text. Etiam at risus et justo dignissim congue. Donec congue lacinia dui, a porttitor lectus condimentum laoreet. Nunc eu."); - label.addStyleName("light"); + label.addStyleName(ValoTheme.LABEL_LIGHT); right.addComponent(label); label = new Label( "Colored type for highlighted text. Etiam at risus et justo dignissim congue. Donec congue lacinia dui, a porttitor lectus condimentum laoreet. Nunc eu."); - label.addStyleName("colored"); + label.addStyleName(ValoTheme.LABEL_COLORED); right.addComponent(label); label = new Label("A label for success"); - label.addStyleName("success"); + label.addStyleName(ValoTheme.LABEL_SUCCESS); right.addComponent(label); label = new Label("A label for failure"); - label.addStyleName("failure"); + label.addStyleName(ValoTheme.LABEL_FAILURE); right.addComponent(label); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/MenuBars.java b/uitest/src/com/vaadin/tests/themes/valo/MenuBars.java index 4a0130931e..fc74166b29 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/MenuBars.java +++ b/uitest/src/com/vaadin/tests/themes/valo/MenuBars.java @@ -25,6 +25,7 @@ import com.vaadin.ui.MenuBar.Command; import com.vaadin.ui.MenuBar.MenuItem; import com.vaadin.ui.Notification; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class MenuBars extends VerticalLayout implements View { public MenuBars() { @@ -32,7 +33,7 @@ public class MenuBars extends VerticalLayout implements View { setSpacing(true); Label h1 = new Label("Menu Bars"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); MenuBar menuBar = getMenuBar(); @@ -41,37 +42,37 @@ public class MenuBars extends VerticalLayout implements View { menuBar = getMenuBar(); menuBar.setCaption("Small style"); - menuBar.addStyleName("small"); + menuBar.addStyleName(ValoTheme.MENUBAR_SMALL); addComponent(menuBar); menuBar = getMenuBar(); menuBar.setCaption("Borderless style"); - menuBar.addStyleName("borderless"); + menuBar.addStyleName(ValoTheme.MENUBAR_BORDERLESS); addComponent(menuBar); menuBar = getMenuBar(); menuBar.setCaption("Small borderless style"); - menuBar.addStyleName("borderless"); - menuBar.addStyleName("small"); + menuBar.addStyleName(ValoTheme.MENUBAR_BORDERLESS); + menuBar.addStyleName(ValoTheme.MENUBAR_SMALL); addComponent(menuBar); Label h2 = new Label("Drop Down Button"); - h2.addStyleName("h2"); + h2.addStyleName(ValoTheme.LABEL_H2); addComponent(h2); HorizontalLayout wrap = new HorizontalLayout(); - wrap.addStyleName("wrapping"); + wrap.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); wrap.setSpacing(true); addComponent(wrap); wrap.addComponent(getMenuButton("Normal", false)); MenuBar split = getMenuButton("Small", false); - split.addStyleName("small"); + split.addStyleName(ValoTheme.MENUBAR_SMALL); wrap.addComponent(split); split = getMenuButton("Borderless", false); - split.addStyleName("borderless"); + split.addStyleName(ValoTheme.MENUBAR_BORDERLESS); wrap.addComponent(split); split = getMenuButton("Themed", false); @@ -80,26 +81,26 @@ public class MenuBars extends VerticalLayout implements View { split = getMenuButton("Small", false); split.addStyleName("color1"); - split.addStyleName("small"); + split.addStyleName(ValoTheme.MENUBAR_SMALL); wrap.addComponent(split); h2 = new Label("Split Button"); - h2.addStyleName("h2"); + h2.addStyleName(ValoTheme.LABEL_H2); addComponent(h2); wrap = new HorizontalLayout(); - wrap.addStyleName("wrapping"); + wrap.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); wrap.setSpacing(true); addComponent(wrap); wrap.addComponent(getMenuButton("Normal", true)); split = getMenuButton("Small", true); - split.addStyleName("small"); + split.addStyleName(ValoTheme.MENUBAR_SMALL); wrap.addComponent(split); split = getMenuButton("Borderless", true); - split.addStyleName("borderless"); + split.addStyleName(ValoTheme.MENUBAR_BORDERLESS); wrap.addComponent(split); split = getMenuButton("Themed", true); @@ -108,7 +109,7 @@ public class MenuBars extends VerticalLayout implements View { split = getMenuButton("Small", true); split.addStyleName("color1"); - split.addStyleName("small"); + split.addStyleName(ValoTheme.MENUBAR_SMALL); wrap.addComponent(split); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/NativeSelects.java b/uitest/src/com/vaadin/tests/themes/valo/NativeSelects.java index 284f7c8d6e..e9c1c78049 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/NativeSelects.java +++ b/uitest/src/com/vaadin/tests/themes/valo/NativeSelects.java @@ -23,17 +23,18 @@ import com.vaadin.ui.ListSelect; import com.vaadin.ui.NativeSelect; import com.vaadin.ui.TwinColSelect; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class NativeSelects extends VerticalLayout implements View { public NativeSelects() { setMargin(true); Label h1 = new Label("Selects"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); diff --git a/uitest/src/com/vaadin/tests/themes/valo/NotificationStyleTest.java b/uitest/src/com/vaadin/tests/themes/valo/NotificationStyleTest.java index 7adae9ce65..5f9542b6f3 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/NotificationStyleTest.java +++ b/uitest/src/com/vaadin/tests/themes/valo/NotificationStyleTest.java @@ -27,6 +27,7 @@ import org.openqa.selenium.support.ui.ExpectedCondition; import com.vaadin.testbench.elements.ButtonElement; import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.ui.themes.ValoTheme; /** * Test for H1 and P elements styles in Notifications. @@ -45,7 +46,8 @@ public class NotificationStyleTest extends MultiBrowserTest { waitUntil(notificationPresentCondition(), 2); WebElement notification = findElement(By.className("v-Notification")); - List<WebElement> headers = notification.findElements(By.tagName("h1")); + List<WebElement> headers = notification.findElements(By + .tagName(ValoTheme.LABEL_H1)); String textAlign = headers.get(0).getCssValue("text-align"); String textAlignInnerHeader = headers.get(1).getCssValue("text-align"); Assert.assertNotEquals("Styles for notification defined h1 tag " diff --git a/uitest/src/com/vaadin/tests/themes/valo/Panels.java b/uitest/src/com/vaadin/tests/themes/valo/Panels.java index 8a17244693..d98daf7b05 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Panels.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Panels.java @@ -27,17 +27,18 @@ import com.vaadin.ui.MenuBar; import com.vaadin.ui.MenuBar.MenuItem; import com.vaadin.ui.Panel; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class Panels extends VerticalLayout implements View { public Panels() { setMargin(true); Label h1 = new Label("Panels & Layout panels"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); TestIcon testIcon = new TestIcon(60); @@ -74,33 +75,33 @@ public class Panels extends VerticalLayout implements View { panel = new Panel("Borderless style"); panel.setIcon(testIcon.get()); - panel.addStyleName("borderless"); + panel.addStyleName(ValoTheme.PANEL_BORDERLESS); panel.setContent(panelContent()); row.addComponent(panel); panel = new Panel("Borderless + scroll divider"); panel.setIcon(testIcon.get()); - panel.addStyleName("borderless"); - panel.addStyleName("scroll-divider"); + panel.addStyleName(ValoTheme.PANEL_BORDERLESS); + panel.addStyleName(ValoTheme.PANEL_SCROLL_INDICATOR); panel.setContent(panelContentScroll()); panel.setHeight("17em"); row.addComponent(panel); panel = new Panel("Well style"); panel.setIcon(testIcon.get()); - panel.addStyleName("well"); + panel.addStyleName(ValoTheme.PANEL_WELL); panel.setContent(panelContent()); row.addComponent(panel); CssLayout layout = new CssLayout(); layout.setIcon(testIcon.get()); layout.setCaption("Panel style layout"); - layout.addStyleName("card"); + layout.addStyleName(ValoTheme.LAYOUT_CARD); layout.addComponent(panelContent()); row.addComponent(layout); layout = new CssLayout(); - layout.addStyleName("card"); + layout.addStyleName(ValoTheme.LAYOUT_CARD); row.addComponent(layout); HorizontalLayout panelCaption = new HorizontalLayout(); panelCaption.addStyleName("v-panel-caption"); @@ -112,13 +113,13 @@ public class Panels extends VerticalLayout implements View { Button action = new Button(); action.setIcon(FontAwesome.PENCIL); - action.addStyleName("borderless-colored"); - action.addStyleName("small"); - action.addStyleName("icon-only"); + action.addStyleName(ValoTheme.BUTTON_BORDERLESS_COLORED); + action.addStyleName(ValoTheme.BUTTON_SMALL); + action.addStyleName(ValoTheme.BUTTON_ICON_ONLY); panelCaption.addComponent(action); MenuBar dropdown = new MenuBar(); - dropdown.addStyleName("borderless"); - dropdown.addStyleName("small"); + dropdown.addStyleName(ValoTheme.MENUBAR_BORDERLESS); + dropdown.addStyleName(ValoTheme.MENUBAR_SMALL); MenuItem addItem = dropdown.addItem("", FontAwesome.CHEVRON_DOWN, null); addItem.setStyleName("icon-only"); addItem.addItem("Settings", null); @@ -134,7 +135,7 @@ public class Panels extends VerticalLayout implements View { layout = new CssLayout(); layout.setIcon(testIcon.get()); layout.setCaption("Well style layout"); - layout.addStyleName("well"); + layout.addStyleName(ValoTheme.LAYOUT_WELL); layout.addComponent(panelContent()); row.addComponent(layout); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/PopupViews.java b/uitest/src/com/vaadin/tests/themes/valo/PopupViews.java index c15270400c..58988c06d6 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/PopupViews.java +++ b/uitest/src/com/vaadin/tests/themes/valo/PopupViews.java @@ -24,17 +24,18 @@ import com.vaadin.ui.Label; import com.vaadin.ui.PopupView; import com.vaadin.ui.PopupView.Content; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class PopupViews extends VerticalLayout implements View { public PopupViews() { setMargin(true); Label h1 = new Label("Popup Views"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); diff --git a/uitest/src/com/vaadin/tests/themes/valo/Sliders.java b/uitest/src/com/vaadin/tests/themes/valo/Sliders.java index 8ed846e39f..9642cb5ccf 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Sliders.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Sliders.java @@ -23,17 +23,18 @@ import com.vaadin.ui.Label; import com.vaadin.ui.ProgressBar; import com.vaadin.ui.Slider; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class Sliders extends VerticalLayout implements View { public Sliders() { setMargin(true); Label h1 = new Label("Sliders"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -67,7 +68,7 @@ public class Sliders extends VerticalLayout implements View { slider = new Slider("No indicator"); slider.setValue(50.0); slider.setWidth("200px"); - slider.addStyleName("no-indicator"); + slider.addStyleName(ValoTheme.SLIDER_NO_INDICATOR); row.addComponent(slider); slider = new Slider("With ticks (not in IE8 & IE9)"); @@ -119,7 +120,7 @@ public class Sliders extends VerticalLayout implements View { slider = new Slider("No indicator"); slider.setValue(50.0); slider.setHeight("200px"); - slider.addStyleName("no-indicator"); + slider.addStyleName(ValoTheme.SLIDER_NO_INDICATOR); slider.setOrientation(SliderOrientation.VERTICAL); row.addComponent(slider); @@ -137,11 +138,11 @@ public class Sliders extends VerticalLayout implements View { row.addComponent(slider); h1 = new Label("Progress Bars"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -154,7 +155,7 @@ public class Sliders extends VerticalLayout implements View { pb2 = new ProgressBar(); pb2.setCaption("Point style"); pb2.setWidth("300px"); - pb2.addStyleName("point"); + pb2.addStyleName(ValoTheme.PROGRESSBAR_POINT); // pb2.setValue(0.6f); row.addComponent(pb2); diff --git a/uitest/src/com/vaadin/tests/themes/valo/SplitPanels.java b/uitest/src/com/vaadin/tests/themes/valo/SplitPanels.java index 9a6d86ae04..4983bc5813 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/SplitPanels.java +++ b/uitest/src/com/vaadin/tests/themes/valo/SplitPanels.java @@ -23,20 +23,21 @@ import com.vaadin.ui.HorizontalSplitPanel; import com.vaadin.ui.Label; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.VerticalSplitPanel; +import com.vaadin.ui.themes.ValoTheme; public class SplitPanels extends VerticalLayout implements View { public SplitPanels() { setMargin(true); Label h1 = new Label("Split Panels"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); addComponent(new Label( "Outlines are just to show the areas of the SplitPanels. They are not part of the actual component style.")); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); row.setMargin(new MarginInfo(true, false, false, false)); addComponent(row); @@ -61,7 +62,7 @@ public class SplitPanels extends VerticalLayout implements View { sp.setCaption("Large style"); sp.setWidth("300px"); sp.setHeight("200px"); - sp.addStyleName("large"); + sp.addStyleName(ValoTheme.SPLITPANEL_LARGE); sp.setFirstComponent(getContent()); sp.setSecondComponent(getContent()); row.addComponent(sp); @@ -70,7 +71,7 @@ public class SplitPanels extends VerticalLayout implements View { sp2.setCaption("Large style"); sp2.setWidth("300px"); sp2.setHeight("200px"); - sp2.addStyleName("large"); + sp2.addStyleName(ValoTheme.SPLITPANEL_LARGE); sp2.setFirstComponent(getContent()); sp2.setSecondComponent(getContent()); row.addComponent(sp2); diff --git a/uitest/src/com/vaadin/tests/themes/valo/Tables.java b/uitest/src/com/vaadin/tests/themes/valo/Tables.java index fb6638ee7d..071e6b746a 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Tables.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Tables.java @@ -42,6 +42,7 @@ import com.vaadin.ui.Table.TableDragMode; import com.vaadin.ui.TextField; import com.vaadin.ui.TreeTable; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class Tables extends VerticalLayout implements View { @@ -72,11 +73,11 @@ public class Tables extends VerticalLayout implements View { setSpacing(true); Label h1 = new Label("Tables"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout wrap = new HorizontalLayout(); - wrap.addStyleName("wrapping"); + wrap.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); wrap.setSpacing(true); addComponent(wrap); @@ -189,9 +190,9 @@ public class Tables extends VerticalLayout implements View { Object columnId) { TextField tf = new TextField(); tf.setInputPrompt("Type here…"); - // tf.addStyleName("compact"); + // tf.addStyleName(ValoTheme.TABLE_COMPACT); if ((Integer) itemId % 2 == 0) { - tf.addStyleName("borderless"); + tf.addStyleName(ValoTheme.TABLE_BORDERLESS); } return tf; } @@ -203,9 +204,9 @@ public class Tables extends VerticalLayout implements View { public Object generateCell(Table source, Object itemId, Object columnId) { DateField tf = new DateField(); - tf.addStyleName("compact"); + tf.addStyleName(ValoTheme.TABLE_COMPACT); if ((Integer) itemId % 2 == 0) { - tf.addStyleName("borderless"); + tf.addStyleName(ValoTheme.DATEFIELD_BORDERLESS); } return tf; } @@ -218,9 +219,9 @@ public class Tables extends VerticalLayout implements View { Object columnId) { ComboBox tf = new ComboBox(); tf.setInputPrompt("Select"); - tf.addStyleName("compact"); + tf.addStyleName(ValoTheme.TABLE_COMPACT); if ((Integer) itemId % 2 == 0) { - tf.addStyleName("borderless"); + tf.addStyleName(ValoTheme.DATEFIELD_BORDERLESS); } return tf; } @@ -232,7 +233,7 @@ public class Tables extends VerticalLayout implements View { public Object generateCell(Table source, Object itemId, Object columnId) { Button b = new Button("Button"); - b.addStyleName("small"); + b.addStyleName(ValoTheme.BUTTON_SMALL); return b; } }); @@ -244,7 +245,7 @@ public class Tables extends VerticalLayout implements View { Object columnId) { Label label = new Label("Label component"); label.setSizeUndefined(); - label.addStyleName("bold"); + label.addStyleName(ValoTheme.LABEL_BOLD); return label; } }); @@ -267,7 +268,7 @@ public class Tables extends VerticalLayout implements View { OptionGroup op = new OptionGroup(); op.addItem("Male"); op.addItem("Female"); - op.addStyleName("horizontal"); + op.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL); return op; } }); @@ -321,45 +322,45 @@ public class Tables extends VerticalLayout implements View { expandRatios ? 1.0f : 0); if (!stripes) { - table.addStyleName("no-stripes"); + table.addStyleName(ValoTheme.TABLE_NO_STRIPES); } else { - table.removeStyleName("no-stripes"); + table.removeStyleName(ValoTheme.TABLE_NO_STRIPES); } if (!verticalLines) { - table.addStyleName("no-vertical-lines"); + table.addStyleName(ValoTheme.TABLE_NO_VERTICAL_LINES); } else { - table.removeStyleName("no-vertical-lines"); + table.removeStyleName(ValoTheme.TABLE_NO_VERTICAL_LINES); } if (!horizontalLines) { - table.addStyleName("no-horizontal-lines"); + table.addStyleName(ValoTheme.TABLE_NO_HORIZONTAL_LINES); } else { - table.removeStyleName("no-horizontal-lines"); + table.removeStyleName(ValoTheme.TABLE_NO_HORIZONTAL_LINES); } if (borderless) { - table.addStyleName("borderless"); + table.addStyleName(ValoTheme.TABLE_BORDERLESS); } else { - table.removeStyleName("borderless"); + table.removeStyleName(ValoTheme.TABLE_BORDERLESS); } if (!headers) { - table.addStyleName("no-header"); + table.addStyleName(ValoTheme.TABLE_NO_HEADER); } else { - table.removeStyleName("no-header"); + table.removeStyleName(ValoTheme.TABLE_NO_HEADER); } if (compact) { - table.addStyleName("compact"); + table.addStyleName(ValoTheme.TABLE_COMPACT); } else { - table.removeStyleName("compact"); + table.removeStyleName(ValoTheme.TABLE_COMPACT); } if (small) { - table.addStyleName("small"); + table.addStyleName(ValoTheme.TABLE_SMALL); } else { - table.removeStyleName("small"); + table.removeStyleName(ValoTheme.TABLE_SMALL); } if (!rowIndex && !rowCaption && rowIcon) { diff --git a/uitest/src/com/vaadin/tests/themes/valo/Tabsheets.java b/uitest/src/com/vaadin/tests/themes/valo/Tabsheets.java index 5e77292471..421da5ffe7 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Tabsheets.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Tabsheets.java @@ -28,6 +28,7 @@ import com.vaadin.ui.TabSheet.SelectedTabChangeEvent; import com.vaadin.ui.TabSheet.SelectedTabChangeListener; import com.vaadin.ui.TabSheet.Tab; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class Tabsheets extends VerticalLayout implements View { @@ -37,12 +38,12 @@ public class Tabsheets extends VerticalLayout implements View { setMargin(true); Label h1 = new Label("Tabs"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout wrap = new HorizontalLayout(); wrap.setSpacing(true); - wrap.addStyleName("wrapping"); + wrap.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); addComponent(wrap); final CheckBox closable = new CheckBox("Closable"); @@ -66,12 +67,12 @@ public class Tabsheets extends VerticalLayout implements View { wrap.addComponent(disable); Label h3 = new Label("Additional Styles"); - h3.addStyleName("h3"); + h3.addStyleName(ValoTheme.LABEL_H3); addComponent(h3); wrap = new HorizontalLayout(); wrap.setSpacing(true); - wrap.addStyleName("wrapping"); + wrap.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); wrap.setMargin(new MarginInfo(false, false, true, false)); addComponent(wrap); diff --git a/uitest/src/com/vaadin/tests/themes/valo/TextFields.java b/uitest/src/com/vaadin/tests/themes/valo/TextFields.java index 347a683673..be6e430b23 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/TextFields.java +++ b/uitest/src/com/vaadin/tests/themes/valo/TextFields.java @@ -28,6 +28,7 @@ import com.vaadin.ui.RichTextArea; import com.vaadin.ui.TextArea; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class TextFields extends VerticalLayout implements View { private TestIcon testIcon = new TestIcon(140); @@ -36,11 +37,11 @@ public class TextFields extends VerticalLayout implements View { setMargin(true); Label h1 = new Label("Text Fields"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -72,7 +73,7 @@ public class TextFields extends VerticalLayout implements View { tf = new TextField("Error, borderless"); tf.setValue("Something’s wrong"); tf.setComponentError(new UserError("Fix it, now!")); - tf.addStyleName("borderless"); + tf.addStyleName(ValoTheme.TEXTFIELD_BORDERLESS); row.addComponent(tf); tf = new TextField("Read-only"); @@ -83,121 +84,121 @@ public class TextFields extends VerticalLayout implements View { tf = new TextField("Small"); tf.setValue("Field value"); - tf.addStyleName("small"); + tf.addStyleName(ValoTheme.TEXTFIELD_SMALL); row.addComponent(tf); tf = new TextField("Large"); tf.setValue("Field value"); - tf.addStyleName("large"); + tf.addStyleName(ValoTheme.TEXTFIELD_LARGE); tf.setIcon(testIcon.get(true)); row.addComponent(tf); tf = new TextField("Icon inside"); tf.setInputPrompt("Ooh, an icon"); - tf.addStyleName("inline-icon"); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); tf.setIcon(testIcon.get()); row.addComponent(tf); tf = new TextField("Large, Icon inside"); tf.setInputPrompt("Ooh, an icon"); - tf.addStyleName("large"); - tf.addStyleName("inline-icon"); + tf.addStyleName(ValoTheme.TEXTFIELD_LARGE); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); tf.setIcon(testIcon.get()); row.addComponent(tf); tf = new TextField("Small, Icon inside"); tf.setInputPrompt("Ooh, an icon"); - tf.addStyleName("small"); - tf.addStyleName("inline-icon"); + tf.addStyleName(ValoTheme.TEXTFIELD_SMALL); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); tf.setIcon(testIcon.get()); row.addComponent(tf); tf = new TextField("16px supported by default"); tf.setInputPrompt("Image icon"); - tf.addStyleName("inline-icon"); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); tf.setIcon(testIcon.get(true, 16)); row.addComponent(tf); tf = new TextField(); tf.setValue("Font, no caption"); - tf.addStyleName("inline-icon"); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); tf.setIcon(testIcon.get()); row.addComponent(tf); tf = new TextField(); tf.setValue("Image, no caption"); - tf.addStyleName("inline-icon"); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); tf.setIcon(testIcon.get(true, 16)); row.addComponent(tf); CssLayout group = new CssLayout(); - group.addStyleName("v-component-group"); + group.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP); row.addComponent(group); tf = new TextField(); tf.setInputPrompt("Grouped with a button"); - tf.addStyleName("inline-icon"); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); tf.setIcon(testIcon.get()); tf.setWidth("260px"); group.addComponent(tf); Button button = new Button("Do It"); - // button.addStyleName("primary"); + // button.addStyleName(ValoTheme.BUTTON_PRIMARY); group.addComponent(button); tf = new TextField("Borderless"); tf.setInputPrompt("Write here…"); - tf.addStyleName("inline-icon"); - tf.addStyleName("borderless"); + tf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); + tf.addStyleName(ValoTheme.TEXTFIELD_BORDERLESS); tf.setIcon(testIcon.get()); row.addComponent(tf); tf = new TextField("Right-aligned"); tf.setValue("1,234"); - tf.addStyleName("align-right"); + tf.addStyleName(ValoTheme.TEXTFIELD_ALIGN_RIGHT); row.addComponent(tf); tf = new TextField("Centered"); tf.setInputPrompt("Guess what?"); - tf.addStyleName("align-center"); + tf.addStyleName(ValoTheme.TEXTFIELD_ALIGN_CENTER); row.addComponent(tf); PasswordField pwf = new PasswordField("Password"); pwf.setInputPrompt("Secret words"); - pwf.addStyleName("inline-icon"); + pwf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); pwf.setIcon(FontAwesome.LOCK); row.addComponent(pwf); pwf = new PasswordField("Password, right-aligned"); pwf.setInputPrompt("Secret words"); - pwf.addStyleName("inline-icon"); - pwf.addStyleName("align-right"); + pwf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); + pwf.addStyleName(ValoTheme.TEXTFIELD_ALIGN_RIGHT); pwf.setIcon(FontAwesome.LOCK); row.addComponent(pwf); pwf = new PasswordField("Password, centered"); pwf.setInputPrompt("Secret words"); - pwf.addStyleName("inline-icon"); - pwf.addStyleName("align-center"); + pwf.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON); + pwf.addStyleName(ValoTheme.TEXTFIELD_ALIGN_CENTER); pwf.setIcon(FontAwesome.LOCK); row.addComponent(pwf); tf = new TextField("Tiny"); tf.setValue("Field value"); - tf.addStyleName("tiny"); + tf.addStyleName(ValoTheme.TEXTFIELD_TINY); row.addComponent(tf); tf = new TextField("Huge"); tf.setValue("Field value"); - tf.addStyleName("huge"); + tf.addStyleName(ValoTheme.TEXTFIELD_HUGE); row.addComponent(tf); h1 = new Label("Text Areas"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); @@ -228,37 +229,37 @@ public class TextFields extends VerticalLayout implements View { row.addComponent(ta); ta = new TextArea("Small"); - ta.addStyleName("small"); + ta.addStyleName(ValoTheme.TEXTAREA_SMALL); ta.setInputPrompt("Write your comment…"); row.addComponent(ta); ta = new TextArea("Large"); - ta.addStyleName("large"); + ta.addStyleName(ValoTheme.TEXTAREA_LARGE); ta.setInputPrompt("Write your comment…"); row.addComponent(ta); ta = new TextArea("Borderless"); - ta.addStyleName("borderless"); + ta.addStyleName(ValoTheme.TEXTAREA_BORDERLESS); ta.setInputPrompt("Write your comment…"); row.addComponent(ta); ta = new TextArea("Right-aligned"); - ta.addStyleName("align-right"); + ta.addStyleName(ValoTheme.TEXTAREA_ALIGN_RIGHT); ta.setValue("Field value, spanning multiple lines of text"); row.addComponent(ta); ta = new TextArea("Centered"); - ta.addStyleName("align-center"); + ta.addStyleName(ValoTheme.TEXTAREA_ALIGN_CENTER); ta.setValue("Field value, spanning multiple lines of text"); row.addComponent(ta); ta = new TextArea("Tiny"); - ta.addStyleName("tiny"); + ta.addStyleName(ValoTheme.TEXTAREA_TINY); ta.setInputPrompt("Write your comment…"); row.addComponent(ta); ta = new TextArea("Huge"); - ta.addStyleName("huge"); + ta.addStyleName(ValoTheme.TEXTAREA_HUGE); ta.setInputPrompt("Write your comment…"); row.addComponent(ta); diff --git a/uitest/src/com/vaadin/tests/themes/valo/Trees.java b/uitest/src/com/vaadin/tests/themes/valo/Trees.java index cb5657660a..02846d8921 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/Trees.java +++ b/uitest/src/com/vaadin/tests/themes/valo/Trees.java @@ -28,17 +28,18 @@ import com.vaadin.ui.Notification; import com.vaadin.ui.Tree; import com.vaadin.ui.Tree.TreeDragMode; import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.ValoTheme; public class Trees extends VerticalLayout implements View { public Trees() { setMargin(true); Label h1 = new Label("Trees"); - h1.addStyleName("h1"); + h1.addStyleName(ValoTheme.LABEL_H1); addComponent(h1); HorizontalLayout row = new HorizontalLayout(); - row.addStyleName("wrapping"); + row.addStyleName(ValoTheme.LAYOUT_HORIZONTAL_WRAPPING); row.setSpacing(true); addComponent(row); diff --git a/uitest/src/com/vaadin/tests/themes/valo/ValoMenuLayout.java b/uitest/src/com/vaadin/tests/themes/valo/ValoMenuLayout.java index 3a3baa686c..0e62f983a6 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/ValoMenuLayout.java +++ b/uitest/src/com/vaadin/tests/themes/valo/ValoMenuLayout.java @@ -19,6 +19,7 @@ import com.vaadin.ui.Component; import com.vaadin.ui.ComponentContainer; import com.vaadin.ui.CssLayout; import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.themes.ValoTheme; /** * @@ -34,7 +35,7 @@ public class ValoMenuLayout extends HorizontalLayout { public ValoMenuLayout() { setSizeFull(); - menuArea.setPrimaryStyleName("valo-menu"); + menuArea.setPrimaryStyleName(ValoTheme.MENU_ROOT); contentArea.setPrimaryStyleName("valo-content"); contentArea.addStyleName("v-scrollable"); @@ -49,7 +50,7 @@ public class ValoMenuLayout extends HorizontalLayout { } public void addMenu(Component menu) { - menu.addStyleName("valo-menu-part"); + menu.addStyleName(ValoTheme.MENU_PART); menuArea.addComponent(menu); } diff --git a/uitest/src/com/vaadin/tests/themes/valo/ValoThemeUI.java b/uitest/src/com/vaadin/tests/themes/valo/ValoThemeUI.java index 988b3487bd..3bf6fd7ca3 100644 --- a/uitest/src/com/vaadin/tests/themes/valo/ValoThemeUI.java +++ b/uitest/src/com/vaadin/tests/themes/valo/ValoThemeUI.java @@ -194,29 +194,29 @@ public class ValoThemeUI extends UI { Component buildTestMenu() { CssLayout menu = new CssLayout(); - menu.addStyleName("large-icons"); + menu.addStyleName(ValoTheme.MENU_PART_LARGE_ICONS); Label logo = new Label("Va"); logo.setSizeUndefined(); - logo.setPrimaryStyleName("valo-menu-logo"); + logo.setPrimaryStyleName(ValoTheme.MENU_LOGO); menu.addComponent(logo); Button b = new Button( "Reference <span class=\"valo-menu-badge\">3</span>"); b.setIcon(FontAwesome.TH_LIST); - b.setPrimaryStyleName("valo-menu-item"); + b.setPrimaryStyleName(ValoTheme.MENU_ITEM); b.addStyleName("selected"); b.setHtmlContentAllowed(true); menu.addComponent(b); b = new Button("API"); b.setIcon(FontAwesome.BOOK); - b.setPrimaryStyleName("valo-menu-item"); + b.setPrimaryStyleName(ValoTheme.MENU_ITEM); menu.addComponent(b); b = new Button("Examples <span class=\"valo-menu-badge\">12</span>"); b.setIcon(FontAwesome.TABLE); - b.setPrimaryStyleName("valo-menu-item"); + b.setPrimaryStyleName(ValoTheme.MENU_ITEM); b.setHtmlContentAllowed(true); menu.addComponent(b); @@ -250,7 +250,7 @@ public class ValoThemeUI extends UI { HorizontalLayout top = new HorizontalLayout(); top.setWidth("100%"); top.setDefaultComponentAlignment(Alignment.MIDDLE_LEFT); - top.addStyleName("valo-menu-title"); + top.addStyleName(ValoTheme.MENU_TITLE); menu.addComponent(top); menu.addComponent(createThemeSelect()); @@ -297,8 +297,8 @@ public class ValoThemeUI extends UI { for (final Entry<String, String> item : menuItems.entrySet()) { if (item.getKey().equals("labels")) { label = new Label("Components", ContentMode.HTML); - label.setPrimaryStyleName("valo-menu-subtitle"); - label.addStyleName("h4"); + label.setPrimaryStyleName(ValoTheme.MENU_SUBTITLE); + label.addStyleName(ValoTheme.LABEL_H4); label.setSizeUndefined(); menuItemsLayout.addComponent(label); } @@ -308,8 +308,8 @@ public class ValoThemeUI extends UI { + "</span>"); count = 0; label = new Label("Containers", ContentMode.HTML); - label.setPrimaryStyleName("valo-menu-subtitle"); - label.addStyleName("h4"); + label.setPrimaryStyleName(ValoTheme.MENU_SUBTITLE); + label.addStyleName(ValoTheme.LABEL_H4); label.setSizeUndefined(); menuItemsLayout.addComponent(label); } @@ -319,8 +319,8 @@ public class ValoThemeUI extends UI { + "</span>"); count = 0; label = new Label("Other", ContentMode.HTML); - label.setPrimaryStyleName("valo-menu-subtitle"); - label.addStyleName("h4"); + label.setPrimaryStyleName(ValoTheme.MENU_SUBTITLE); + label.addStyleName(ValoTheme.LABEL_H4); label.setSizeUndefined(); menuItemsLayout.addComponent(label); } @@ -335,7 +335,7 @@ public class ValoThemeUI extends UI { + " <span class=\"valo-menu-badge\">123</span>"); } b.setHtmlContentAllowed(true); - b.setPrimaryStyleName("valo-menu-item"); + b.setPrimaryStyleName(ValoTheme.MENU_ITEM); b.setIcon(testIcon.get()); menuItemsLayout.addComponent(b); count++; diff --git a/uitest/src/com/vaadin/tests/tooltip/MenuBarTooltip.java b/uitest/src/com/vaadin/tests/tooltip/MenuBarTooltip.java new file mode 100644 index 0000000000..ff470336f5 --- /dev/null +++ b/uitest/src/com/vaadin/tests/tooltip/MenuBarTooltip.java @@ -0,0 +1,34 @@ +package com.vaadin.tests.tooltip; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.MenuBar; + +public class MenuBarTooltip extends AbstractTestUI { + + @Override + protected void setup(VaadinRequest request) { + MenuBar menubar = new MenuBar(); + + MenuBar.MenuItem menuitem = menubar.addItem("Menu item", null, null); + menuitem.setDescription("Menu item description"); + + MenuBar.MenuItem submenuitem1 = menuitem.addItem("Submenu item 1", null, null); + submenuitem1.setDescription("Submenu item 1 description"); + + MenuBar.MenuItem submenuitem2 = menuitem.addItem("Submenu item 2", null, null); + submenuitem2.setDescription("Submenu item 2 description"); + + addComponent(menubar); + } + + @Override + protected Integer getTicketNumber() { + return 14854; + } + + @Override + protected String getTestDescription() { + return "MenuItem tooltip should have a larger z-index than MenuBar/MenuItem."; + } +} diff --git a/uitest/src/com/vaadin/tests/tooltip/MenuBarTooltipTest.java b/uitest/src/com/vaadin/tests/tooltip/MenuBarTooltipTest.java new file mode 100644 index 0000000000..9b2f7d13d6 --- /dev/null +++ b/uitest/src/com/vaadin/tests/tooltip/MenuBarTooltipTest.java @@ -0,0 +1,46 @@ +package com.vaadin.tests.tooltip; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; + +import org.junit.Test; +import org.openqa.selenium.By; + +import com.vaadin.testbench.elements.MenuBarElement; +import com.vaadin.tests.tb3.MultiBrowserTest; +import com.vaadin.ui.themes.ChameleonTheme; +import com.vaadin.ui.themes.Reindeer; +import com.vaadin.ui.themes.Runo; +import com.vaadin.ui.themes.ValoTheme; + +public class MenuBarTooltipTest extends MultiBrowserTest { + + @Test + public void toolTipShouldBeOnTopOfMenuItem() { + String[] themes = new String[] { + ValoTheme.THEME_NAME, + Reindeer.THEME_NAME, + Runo.THEME_NAME, + ChameleonTheme.THEME_NAME + }; + + for(String theme : themes) { + assertZIndices(theme); + } + } + + public void assertZIndices(String theme) { + openTestURL("theme=" + theme); + + $(MenuBarElement.class).first().clickItem("Menu item"); + + assertThat(String.format("Invalid z-index for theme %s.", theme), + getZIndex("v-tooltip"), greaterThan(getZIndex("v-menubar-popup"))); + } + + private int getZIndex(String className) { + return Integer.parseInt( + findElement(By.className(className)).getCssValue("z-index")); + } + +}
\ No newline at end of file diff --git a/uitest/src/com/vaadin/tests/widgetset/TestingWidgetSet.gwt.xml b/uitest/src/com/vaadin/tests/widgetset/TestingWidgetSet.gwt.xml index 8a02d91d2c..3878e85193 100644 --- a/uitest/src/com/vaadin/tests/widgetset/TestingWidgetSet.gwt.xml +++ b/uitest/src/com/vaadin/tests/widgetset/TestingWidgetSet.gwt.xml @@ -23,5 +23,6 @@ <generate-with class="com.vaadin.tests.widgetset.rebind.TestWidgetRegistryGenerator"> <when-type-is class="com.vaadin.tests.widgetset.client.TestWidgetConnector.TestWidgetRegistry" /> </generate-with> - + + <entry-point class="com.vaadin.tests.widgetset.client.TestingWidgetsetEntryPoint" /> </module> diff --git a/uitest/src/com/vaadin/tests/widgetset/client/ProfilerCompilationCanary.java b/uitest/src/com/vaadin/tests/widgetset/client/ProfilerCompilationCanary.java new file mode 100644 index 0000000000..d5ab1da2f9 --- /dev/null +++ b/uitest/src/com/vaadin/tests/widgetset/client/ProfilerCompilationCanary.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.widgetset.client; + +import com.google.gwt.user.client.ui.Label; +import com.vaadin.client.Profiler; + +public class ProfilerCompilationCanary extends Label { + public ProfilerCompilationCanary() { + if (Profiler.isEnabled()) { + setText("Test does not work when profiler is enabled {dummyCode;}"); + } else { + setText(getCanaryCode()); + } + } + + /* + * Finds the native js function for the canaryWithProfiler method and gets a + * string representation of it, which in most browsers produces the actual + * method implementation that we want to verify has an empty body. + */ + private static native String getCanaryCode() + /*-{ + return @ProfilerCompilationCanary::canaryWithProfiler(*).toString(); + }-*/; + + /* + * We don't care about running this method, we just want to make sure that + * the generated implementation is empty. + */ + public static void canaryWithProfiler() { + Profiler.enter("canaryWithProfiler"); + Profiler.leave("canaryWithProfiler"); + } +} diff --git a/uitest/src/com/vaadin/tests/widgetset/client/TestingWidgetsetEntryPoint.java b/uitest/src/com/vaadin/tests/widgetset/client/TestingWidgetsetEntryPoint.java new file mode 100644 index 0000000000..7268d02993 --- /dev/null +++ b/uitest/src/com/vaadin/tests/widgetset/client/TestingWidgetsetEntryPoint.java @@ -0,0 +1,83 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.widgetset.client; + +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.user.client.Window.Location; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ValueMap; +import com.vaadin.client.debug.internal.DebugButton; +import com.vaadin.client.debug.internal.Icon; +import com.vaadin.client.debug.internal.Section; +import com.vaadin.client.debug.internal.VDebugWindow; + +public class TestingWidgetsetEntryPoint implements EntryPoint { + + @Override + public void onModuleLoad() { + if (Location.getPath().contains("PreserveCustomDebugSectionOpen")) { + addDummyDebugWindowSection(); + } + } + + private void addDummyDebugWindowSection() { + VDebugWindow.get().addSection(new Section() { + private final DebugButton tabButton = new DebugButton(Icon.ERROR, + "Dummy debug window section"); + private final Label controls = new Label(""); + private final Label contents = new Label( + "Dummy debug window section"); + + @Override + public DebugButton getTabButton() { + return tabButton; + } + + @Override + public Widget getControls() { + return controls; + } + + @Override + public Widget getContent() { + return contents; + } + + @Override + public void show() { + // nop + } + + @Override + public void hide() { + // nop + } + + @Override + public void meta(ApplicationConnection ac, ValueMap meta) { + // nop + } + + @Override + public void uidl(ApplicationConnection ac, ValueMap uidl) { + // nop + } + }); + } + +} diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java new file mode 100644 index 0000000000..81a9ab5bf1 --- /dev/null +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/MySelectionModelConnector.java @@ -0,0 +1,61 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.widgetset.client.grid; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.connectors.MultiSelectionModelConnector; +import com.vaadin.client.renderers.ComplexRenderer; +import com.vaadin.client.widget.grid.selection.ClickSelectHandler; +import com.vaadin.client.widget.grid.selection.SelectionModel.Multi; +import com.vaadin.client.widgets.Grid; +import com.vaadin.shared.ui.Connect; +import com.vaadin.tests.components.grid.GridCustomSelectionModel.MySelectionModel; + +import elemental.json.JsonObject; + +@Connect(MySelectionModel.class) +public class MySelectionModelConnector extends MultiSelectionModelConnector { + + private ClickSelectHandler<JsonObject> handler; + + @Override + protected void extend(ServerConnector target) { + super.extend(target); + handler = new ClickSelectHandler<JsonObject>(getGrid()); + } + + @Override + public void onUnregister() { + super.onUnregister(); + handler.removeHandler(); + handler = null; + } + + @Override + protected Multi<JsonObject> createSelectionModel() { + return new MySelectionModel(); + } + + public class MySelectionModel extends MultiSelectionModel { + + @Override + protected ComplexRenderer<Boolean> createSelectionColumnRenderer( + Grid<JsonObject> grid) { + // No Selection Column. + return null; + } + } +} diff --git a/uitest/tb2/com/vaadin/tests/components/OutOfSyncTest.html b/uitest/tb2/com/vaadin/tests/components/OutOfSyncTest.html deleted file mode 100644 index 4828069e2a..0000000000 --- a/uitest/tb2/com/vaadin/tests/components/OutOfSyncTest.html +++ /dev/null @@ -1,38 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head profile="http://selenium-ide.openqa.org/profiles/test-case"> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -<link rel="selenium.base" href="http://localhost:8888/" /> -<title>New Test</title> -</head> -<body> -<table cellpadding="1" cellspacing="1" border="1"> -<thead> -<tr><td rowspan="1" colspan="3">New Test</td></tr> -</thead><tbody> -<tr> - <td>open</td> - <td>/run/OutOfSyncTest?restartApplication</td> - <td></td> -</tr> -<tr> - <td>pause</td> - <td>1000</td> - <td></td> -</tr> -<tr> - <td>click</td> - <td>vaadin=runOutOfSyncTest::/VButton[0]/domChild[0]/domChild[0]</td> - <td></td> -</tr> -<!--Out of sync occured -> the button should be removed--> -<tr> - <td>assertElementNotPresent</td> - <td>vaadin=runOutOfSyncTest::/VButton[0]</td> - <td></td> -</tr> - -</tbody></table> -</body> -</html> diff --git a/uitest/tb2/com/vaadin/tests/components/abstractembedded/EmbeddedWithNullSource.html b/uitest/tb2/com/vaadin/tests/components/abstractembedded/EmbeddedWithNullSource.html deleted file mode 100644 index 37186bd901..0000000000 --- a/uitest/tb2/com/vaadin/tests/components/abstractembedded/EmbeddedWithNullSource.html +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head profile="http://selenium-ide.openqa.org/profiles/test-case"> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -<link rel="selenium.base" href="" /> -<title>EmbeddedWithNullSource</title> -</head> -<body> -<table cellpadding="1" cellspacing="1" border="1"> -<thead> -<tr><td rowspan="1" colspan="3">EmbeddedWithNullSource</td></tr> -</thead><tbody> -<tr> - <td>open</td> - <td>/run/EmbeddedWithNullSource?restartApplication</td> - <td></td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td></td> -</tr> - -</tbody></table> -</body> -</html> diff --git a/uitest/tb2/com/vaadin/tests/components/combobox/ComboBoxLargeIcons.html b/uitest/tb2/com/vaadin/tests/components/combobox/ComboBoxLargeIcons.html deleted file mode 100644 index ff6c82dfdb..0000000000 --- a/uitest/tb2/com/vaadin/tests/components/combobox/ComboBoxLargeIcons.html +++ /dev/null @@ -1,152 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head profile="http://selenium-ide.openqa.org/profiles/test-case"> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -<link rel="selenium.base" href="" /> -<title>New Test</title> -</head> -<body> -<table cellpadding="1" cellspacing="1" border="1"> -<thead> -<tr><td rowspan="1" colspan="3">New Test</td></tr> -</thead><tbody> -<tr> - <td>open</td> - <td>/run/com.vaadin.tests.components.combobox.Comboboxes?restartApplication</td> - <td></td> -</tr> -<tr> - <td>select</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::PID_Sselectaction-Icon/domChild[0]</td> - <td>label=16x16</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VFilterSelect[0]/domChild[1]</td> - <td>13,8</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::Root/VFilterSelect$SuggestionPopup[0]/domChild[0]/domChild[2]/domChild[0]</td> - <td>116,6</td> -</tr> -<!-- Open twice to avoid IE6 css issues --> -<tr> - <td>open</td> - <td>/run/com.vaadin.tests.components.combobox.Comboboxes?restartApplication</td> - <td></td> -</tr> -<tr> - <td>select</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::PID_Sselectaction-Icon/domChild[0]</td> - <td>label=16x16</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VFilterSelect[0]/domChild[1]</td> - <td>13,8</td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>icons-16x16-page1</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::Root/VFilterSelect$SuggestionPopup[0]/domChild[0]/domChild[2]/domChild[0]</td> - <td>116,6</td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>icons-16x16-page2</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::Root/VFilterSelect$SuggestionPopup[0]/VFilterSelect$SuggestionMenu[0]#item0</td> - <td>378,1</td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>icons-16x16-selected-1-3-5-9</td> -</tr> -<tr> - <td>select</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::PID_Sselectaction-Icon/domChild[0]</td> - <td>label=32x32</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[4]/VFilterSelect[0]/domChild[2]</td> - <td>8,13</td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>icons-32x32-page2</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/domChild[1]</td> - <td>409,27</td> -</tr> -<tr> - <td>select</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::PID_Sselectaction-Icon/domChild[0]</td> - <td>label=64x64</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[9]/VFilterSelect[0]/domChild[1]</td> - <td>11,13</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::Root/VFilterSelect$SuggestionPopup[0]/VFilterSelect$SuggestionMenu[0]#item1</td> - <td>213,57</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/domChild[4]</td> - <td>535,43</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[8]/VFilterSelect[0]/domChild[1]</td> - <td>7,12</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::Root/VFilterSelect$SuggestionPopup[0]/VFilterSelect$SuggestionMenu[0]#item1</td> - <td>158,25</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[7]/VFilterSelect[0]/domChild[0]</td> - <td>16,9</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[7]/VFilterSelect[0]/domChild[0]</td> - <td>80,7</td> -</tr> -<tr> - <td>pressSpecialKey</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[7]/VFilterSelect[0]/domChild[0]</td> - <td>down</td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>icons-64x64-page1-highlight-first</td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentscomboboxComboboxes::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/domChild[6]/domChild[0]</td> - <td>510,1</td> -</tr> -</tbody></table> -</body> -</html> diff --git a/uitest/tb2/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.html b/uitest/tb2/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.html deleted file mode 100644 index 4dc63721a1..0000000000 --- a/uitest/tb2/com/vaadin/tests/components/table/ColumnCollapsingAndColumnExpansion.html +++ /dev/null @@ -1,97 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head profile="http://selenium-ide.openqa.org/profiles/test-case"> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> -<link rel="selenium.base" href="" /> -<title>ColumnCollapsingAndColumnExpansion</title> -</head> -<body> -<table cellpadding="1" cellspacing="1" border="1"> -<thead> -<tr><td rowspan="1" colspan="3">ColumnCollapsingAndColumnExpansion</td></tr> -</thead><tbody> -<tr> - <td>open</td> - <td>/run/com.vaadin.tests.components.table.ColumnCollapsingAndColumnExpansion?restartApplication</td> - <td></td> -</tr> -<!--Initial state, all 3 columns visible--> -<tr> - <td>screenCapture</td> - <td></td> - <td>col1-col2-col3-visible</td> -</tr> -<tr> - <td>click</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[0]/domChild[1]</td> - <td></td> -</tr> -<!--Hide 'col2' through table interface--> -<tr> - <td>mouseClick</td> - <td>//tr[2]/td/span/div</td> - <td>23,2</td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>col2-hidden</td> -</tr> -<!--Hide 'Col1' using button--> -<tr> - <td>enterCharacter</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VTextField[0]</td> - <td>Col1</td> -</tr> -<tr> - <td>click</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[1]/VButton[0]/domChild[0]/domChild[0]</td> - <td></td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>col1-col2-hidden</td> -</tr> -<!--Show 'col2' using action handler--> -<tr> - <td>contextmenu</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[0]/VScrollTable[0]/domChild[1]/domChild[0]/domChild[1]/domChild[0]/domChild[2]/domChild[0]/domChild[0]</td> - <td></td> -</tr> -<tr> - <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VContextMenu[0]#option0</td> - <td>11,6</td> -</tr> -<tr> - <td>enterCharacter</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VTextField[0]</td> - <td>Col2</td> -</tr> -<tr> - <td>screenCapture</td> - <td></td> - <td>col1-hidden</td> -</tr> -<!--Show 'Col1' using button--> -<tr> - <td>enterCharacter</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[0]/VTextField[0]</td> - <td>Col1</td> -</tr> -<tr> - <td>click</td> - <td>vaadin=runcomvaadintestscomponentstableColumnCollapsingAndColumnExpansion::/VVerticalLayout[0]/ChildComponentContainer[1]/VVerticalLayout[0]/ChildComponentContainer[1]/VHorizontalLayout[0]/ChildComponentContainer[2]/VButton[0]/domChild[0]/domChild[0]</td> - <td></td> -</tr> -<!--We should now be back at the initial state, all 3 columns visible--> -<tr> - <td>screenCapture</td> - <td></td> - <td>col1-col2-col3-visible-again</td> -</tr> -</tbody></table> -</body> -</html> diff --git a/uitest/tb2/com/vaadin/tests/components/tabsheet/TabSheetBasicOperations.html b/uitest/tb2/com/vaadin/tests/components/tabsheet/TabSheetBasicOperations.html index f0f08efb58..92e2e05197 100644 --- a/uitest/tb2/com/vaadin/tests/components/tabsheet/TabSheetBasicOperations.html +++ b/uitest/tb2/com/vaadin/tests/components/tabsheet/TabSheetBasicOperations.html @@ -35,7 +35,7 @@ </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item3</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item4</td> <td>49,10</td> </tr> <tr> @@ -60,7 +60,7 @@ </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item3</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item4</td> <td>49,5</td> </tr> <tr> @@ -85,7 +85,7 @@ </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item3</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item4</td> <td>74,11</td> </tr> <tr> @@ -110,7 +110,7 @@ </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item3</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item4</td> <td>89,4</td> </tr> <tr> @@ -252,12 +252,12 @@ </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item4</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item3</td> <td>49,4</td> </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[1]/VMenuBar[0]#item2</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[1]/VMenuBar[0]#item4</td> <td>80,12</td> </tr> <tr> @@ -267,12 +267,12 @@ </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item4</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[0]/VMenuBar[0]#item3</td> <td>67,14</td> </tr> <tr> <td>mouseClick</td> - <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[1]/VMenuBar[0]#item3</td> + <td>vaadin=runcomvaadintestscomponentstabsheetTabSheetTest::Root/VOverlay[1]/VMenuBar[0]#item5</td> <td>71,9</td> </tr> <!--show log to be able to assert--> |