Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: disable client side validation #973

Merged
merged 9 commits into from
May 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@
<groupId>com.vaadin</groupId>
<artifactId>flow-polymer-template</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-grid-flow</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-grid-testbench</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2000-2021 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.flow.component.select.examples;

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.Route;

@Route("vaadin-select/override-client-validation")
public class OverrideClientValidationPage extends Div {

public static final String ID_BASIC_SELECT = "basic-select";
public static final String ID_BASIC_SELECT_RESULT_SPAN = "basic-select-result-span";
public static final String ID_SET_INVALID_BUTTON = "set-invalid-button";
public static final String ID_LOG_BUTTON = "log-button";
public static final String ID_DETACH_BUTTON = "detach-button";
public static final String ID_REATTACH_BUTTON = "reattach-button";

private Select<String> basicSelect;
private Span basicSelectResultSpan;
private Select<String> selectInGrid;

public OverrideClientValidationPage() {
createBasicSetup();
createGridSetup();
createActions();
}

private void createBasicSetup() {
basicSelect = new Select<>("a", "b", "c");
basicSelect.setId(ID_BASIC_SELECT);

basicSelectResultSpan = new Span();
basicSelectResultSpan.setId(ID_BASIC_SELECT_RESULT_SPAN);

add(new H1("Basic select usage"), basicSelect, basicSelectResultSpan);
}

private void createGridSetup() {
selectInGrid = new Select<>();
Grid<String> grid = new Grid<>();
grid.setItems("test");
grid.addColumn(new ComponentRenderer<>(item -> selectInGrid,
(component, item) -> component));
add(new H1("Grid select usage"), grid);
}

private void createActions() {
NativeButton setInvalidButton = new NativeButton("Set all invalid",
e -> {
basicSelect.setInvalid(true);
selectInGrid.setInvalid(true);
});
setInvalidButton.setId(ID_SET_INVALID_BUTTON);

NativeButton logButton = new NativeButton("Log validation state",
e -> basicSelectResultSpan.setText(
basicSelect.isInvalid() ? "invalid" : "valid"));
logButton.setId(ID_LOG_BUTTON);

NativeButton detachButton = new NativeButton("Detach select",
e -> this.remove(basicSelect));
detachButton.setId(ID_DETACH_BUTTON);

NativeButton reattachButton = new NativeButton("Reattach select",
e -> this.addComponentAsFirst(basicSelect));
reattachButton.setId(ID_REATTACH_BUTTON);

addComponentAsFirst(new Div(setInvalidButton, logButton, detachButton,
reattachButton));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
@TestPath("vaadin-select/")
public class BasicFeaturesIT extends AbstractSelectIT {

@Test
public void test_initialClientValue() {
Assert.assertEquals("", selectElement.getProperty("value"));
}

@Test
public void testEnabled_disabling_userCannotSelect() {
page.toggleEnabled(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2000-2021 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.flow.component.select.test;

import com.vaadin.flow.component.grid.testbench.GridElement;
import com.vaadin.flow.component.select.examples.OverrideClientValidationPage;
import com.vaadin.flow.component.select.testbench.SelectElement;
import com.vaadin.flow.testutil.TestPath;
import com.vaadin.testbench.TestBenchElement;
import com.vaadin.tests.AbstractComponentIT;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

@TestPath("vaadin-select/override-client-validation")
public class OverrideClientValidationIT extends AbstractComponentIT {

private SelectElement basicSelectElement;
private TestBenchElement setInvalidButton;
private TestBenchElement logButton;
private TestBenchElement detachButton;
private TestBenchElement reattachButton;
private TestBenchElement basicSelectResultSpan;
private SelectElement selectInGridElement;

@Before
public void setUp() {
open();
basicSelectElement = $(SelectElement.class)
.id(OverrideClientValidationPage.ID_BASIC_SELECT);
setInvalidButton = $("button")
.id(OverrideClientValidationPage.ID_SET_INVALID_BUTTON);
logButton = $("button").id(OverrideClientValidationPage.ID_LOG_BUTTON);
detachButton = $("button")
.id(OverrideClientValidationPage.ID_DETACH_BUTTON);
reattachButton = $("button")
.id(OverrideClientValidationPage.ID_REATTACH_BUTTON);
basicSelectResultSpan = $("span")
.id(OverrideClientValidationPage.ID_BASIC_SELECT_RESULT_SPAN);
GridElement gridElement = $(GridElement.class).first();
selectInGridElement = gridElement.$(SelectElement.class).first();
}

@Test
public void testTriggeringClientValidationShouldNotOverrideClientValidationState() {
// Set server state to invalid
setInvalidButton.click();
assertClientSideSelectValidationState(basicSelectElement, false);

// Trigger client side validation
triggerClientSideValidation(basicSelectElement);
// Client side state should still be invalid
assertClientSideSelectValidationState(basicSelectElement, false);
}

@Test
public void testModifyingClientSideValidationStateShouldNotAffectServerSideValidationState() {
// Set server state to invalid
setInvalidButton.click();
logButton.click();
Assert.assertEquals("invalid", basicSelectResultSpan.getText());

// Overwrite client side validation state to be valid
overwriteClientSideValidationState(basicSelectElement, true);
// Server state should still be invalid
logButton.click();
Assert.assertEquals("invalid", basicSelectResultSpan.getText());
}

@Test
public void testDetachingAndReattachingShouldStillOverrideClientValidation() {
// Set server state to invalid
setInvalidButton.click();
assertClientSideSelectValidationState(basicSelectElement, false);

// Detach and reattach
detachButton.click();
reattachButton.click();
// Need to refresh Selenium reference
basicSelectElement = $(SelectElement.class)
.id(OverrideClientValidationPage.ID_BASIC_SELECT);

// Client side state should still be invalid after reattaching
assertClientSideSelectValidationState(basicSelectElement, false);

// Trigger client side validation after reattaching
triggerClientSideValidation(basicSelectElement);
// Client side state should still be invalid after reattaching and
// triggering validation
assertClientSideSelectValidationState(basicSelectElement, false);
}

@Test
public void testTriggeringClientValidationShouldNotOverrideClientValidationStateWhenUsedInComponentRenderer() {
// Set server state to invalid
setInvalidButton.click();
assertClientSideSelectValidationState(selectInGridElement, false);

triggerClientSideValidation(selectInGridElement);

assertClientSideSelectValidationState(selectInGridElement, false);
}

private void assertClientSideSelectValidationState(
SelectElement selectElement, boolean valid) {
Boolean validationState = selectElement.getPropertyBoolean("invalid");

Assert.assertEquals("Validation state did not match", !valid,
validationState);
}

private void triggerClientSideValidation(SelectElement selectElement) {
selectElement.getCommandExecutor()
.executeScript("arguments[0].validate()", selectElement);
getCommandExecutor().waitForVaadin();
}

private void overwriteClientSideValidationState(SelectElement selectElement,
boolean valid) {
selectElement.setProperty("invalid", !valid);
getCommandExecutor().waitForVaadin();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2000-2019 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.flow.component.select;

import com.vaadin.flow.internal.StateNode;

/**
* Utility class for select Flow component to disable client side validation.
*
* @author Vaadin Ltd
*/
final class FieldValidationUtil {
sissbruecker marked this conversation as resolved.
Show resolved Hide resolved

private FieldValidationUtil() {
// utility class should not be instantiated
}

static void disableClientValidation(Select component) {
// Since this method should be called for every time when the component
// is attached to the UI, lets check that it is actually so
if (!component.isAttached()) {
throw new IllegalStateException(String.format(
"Component %s is not attached. Client side "
+ "validation can only be disabled for a component "
+ "when it has been attached to the UI and because "
+ "it should be called again once the component is "
+ "removed/added, you should call this method from "
+ "the onAttach() method of the component.",
component.toString()));
}
// Wait until the response is being written as the validation state
// should not change after that
final StateNode componentNode = component.getElement().getNode();
componentNode.runWhenAttached(ui -> ui.getInternals().getStateTree()
.beforeClientResponse(componentNode,
executionContext -> overrideClientValidation(
component)));
}

private static void overrideClientValidation(Select component) {
// Overwrite client validation method to simply return validation state
// set from server
StringBuilder expression = new StringBuilder(
"this.validate = function () {return !this.invalid;};");

if (component.isInvalid()) {
/*
* By default the invalid flag is set to false. Workaround the case
* where the client side validation overrides the invalid state
* before the validation function itself is overridden above.
*/
expression.append("this.invalid = true;");
}
component.getElement().executeJs(expression.toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@
*/
package com.vaadin.flow.component.select;

import java.io.Serializable;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentUtil;
Expand Down Expand Up @@ -59,6 +51,14 @@
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.shared.Registration;

import java.io.Serializable;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;

/**
* A customizable drop-down select component similar to a native browser select.
* <p>
Expand Down Expand Up @@ -123,6 +123,10 @@ public Select() {

getElement().setProperty("invalid", false);
getElement().setProperty("opened", false);
// Trigger model-to-presentation conversion in constructor, so that
// the client side component has a correct initial value of an empty
// string
setPresentationValue(null);
tulioag marked this conversation as resolved.
Show resolved Hide resolved

getElement().appendChild(listBox.getElement());

Expand Down Expand Up @@ -746,6 +750,7 @@ protected boolean hasValidValue() {
protected void onAttach(AttachEvent attachEvent) {
super.onAttach(attachEvent);
initConnector();
FieldValidationUtil.disableClientValidation(this);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,13 +394,12 @@ protected void setRequired(boolean required) {
* <p>
* Set to true if the value is invalid.
* <p>
* This property is synchronized automatically from client side when a
* 'invalid-changed' event happens.
* This property is not synchronized automatically from the client side, so
* the returned value may not be the same as in client side.
* </p>
*
* @return the {@code invalid} property from the webcomponent
*/
@Synchronize(property = "invalid", value = "invalid-changed")
sissbruecker marked this conversation as resolved.
Show resolved Hide resolved
protected boolean isInvalidBoolean() {
return getElement().getProperty("invalid", false);
}
Expand Down