* Introduce empty selection functionality for NativeSelect. Fixes vaadin/framework8-issues#545tags/8.0.0.beta2
@@ -17,6 +17,7 @@ | |||
package com.vaadin.client.ui.nativeselect; | |||
import com.google.gwt.event.shared.HandlerRegistration; | |||
import com.google.gwt.user.client.ui.ListBox; | |||
import com.vaadin.client.annotations.OnStateChange; | |||
import com.vaadin.client.connectors.AbstractSingleSelectConnector; | |||
import com.vaadin.client.data.DataSource; | |||
@@ -91,6 +92,21 @@ public class NativeSelectConnector | |||
getWidget().setTabIndex(getState().tabIndex); | |||
} | |||
@OnStateChange({ "emptySelectionCaption", "emptySelectionAllowed" }) | |||
private void onEmptySelectionCaptionChange() { | |||
ListBox listBox = getWidget().getListBox(); | |||
boolean hasEmptyItem = listBox.getItemCount() > 0 | |||
&& listBox.getValue(0).isEmpty(); | |||
if (hasEmptyItem && getState().emptySelectionAllowed) { | |||
listBox.setItemText(0, getState().emptySelectionCaption); | |||
} else if (hasEmptyItem && !getState().emptySelectionAllowed) { | |||
listBox.removeItem(0); | |||
} else if (!hasEmptyItem && getState().emptySelectionAllowed) { | |||
listBox.insertItem(getState().emptySelectionCaption, 0); | |||
listBox.setValue(0, ""); | |||
} | |||
} | |||
@Override | |||
public NativeSelectState getState() { | |||
return (NativeSelectState) super.getState(); | |||
@@ -112,9 +128,11 @@ public class NativeSelectConnector | |||
final VNativeSelect select = getWidget(); | |||
final int itemCount = select.getListBox().getItemCount(); | |||
for (int i = range.getStart(); i < range.getEnd(); i++) { | |||
int increment = getState().emptySelectionAllowed ? 1 : 0; | |||
for (int i = range.getStart() + increment; i < range.getEnd() | |||
+ increment; i++) { | |||
final JsonObject row = getDataSource().getRow(i); | |||
final JsonObject row = getDataSource().getRow(i - increment); | |||
if (i < itemCount) { | |||
// Reuse and update an existing item | |||
@@ -127,8 +145,8 @@ public class NativeSelectConnector | |||
} | |||
} | |||
for (int i = select.getListBox().getItemCount() - 1; i >= range | |||
.getEnd(); i--) { | |||
for (int i = select.getListBox().getItemCount() - 1; i >= range.getEnd() | |||
+ increment; i--) { | |||
// Remove extra items if the new dataset is smaller than the old | |||
select.getListBox().removeItem(i); | |||
} |
@@ -17,6 +17,7 @@ | |||
package com.vaadin.ui; | |||
import java.util.Collection; | |||
import java.util.Objects; | |||
import com.vaadin.data.HasDataProvider; | |||
import com.vaadin.data.provider.DataProvider; | |||
@@ -139,4 +140,59 @@ public class NativeSelect<T> extends AbstractSingleSelect<T> | |||
public ItemCaptionGenerator<T> getItemCaptionGenerator() { | |||
return super.getItemCaptionGenerator(); | |||
} | |||
/** | |||
* Returns whether the user is allowed to select nothing in the combo box. | |||
* | |||
* @return true if empty selection is allowed, false otherwise | |||
*/ | |||
public boolean isEmptySelectionAllowed() { | |||
return getState(false).emptySelectionAllowed; | |||
} | |||
/** | |||
* Sets whether the user is allowed to select nothing in the combo box. When | |||
* true, a special empty item is shown to the user. | |||
* | |||
* @param emptySelectionAllowed | |||
* true to allow not selecting anything, false to require | |||
* selection | |||
*/ | |||
public void setEmptySelectionAllowed(boolean emptySelectionAllowed) { | |||
getState().emptySelectionAllowed = emptySelectionAllowed; | |||
} | |||
/** | |||
* Returns the empty selection caption. | |||
* <p> | |||
* The empty string {@code ""} is the default empty selection caption. | |||
* | |||
* @see #setEmptySelectionAllowed(boolean) | |||
* @see #isEmptySelectionAllowed() | |||
* @see #setEmptySelectionCaption(String) | |||
* @see #isSelected(Object) | |||
* | |||
* @return the empty selection caption, not {@code null} | |||
*/ | |||
public String getEmptySelectionCaption() { | |||
return getState(false).emptySelectionCaption; | |||
} | |||
/** | |||
* Sets the empty selection caption. | |||
* <p> | |||
* The empty string {@code ""} is the default empty selection caption. | |||
* <p> | |||
* If empty selection is allowed via the | |||
* {@link #setEmptySelectionAllowed(boolean)} method (it is by default) then | |||
* the empty item will be shown with the given caption. | |||
* | |||
* @param caption | |||
* the caption to set, not {@code null} | |||
* @see #isSelected(Object) | |||
*/ | |||
public void setEmptySelectionCaption(String caption) { | |||
Objects.nonNull(caption); | |||
getState().emptySelectionCaption = caption; | |||
} | |||
} |
@@ -48,12 +48,14 @@ public class ComboBoxDeclarativeTest | |||
int pageLength = 7; | |||
String popupWidth = "11%"; | |||
boolean emptySelectionAllowed = false; | |||
String emptySelectionCaption = "foo"; | |||
String design = String.format( | |||
"<%s placeholder='%s' text-input-allowed='%s' page-length='%d' " | |||
+ "popup-width='%s' empty-selection-allowed='%s' scroll-to-selected-item/>", | |||
+ "popup-width='%s' empty-selection-allowed='%s' " | |||
+ "scroll-to-selected-item empty-selection-caption='%s'/>", | |||
getComponentTag(), placeholder, textInputAllowed, pageLength, | |||
popupWidth, emptySelectionAllowed); | |||
popupWidth, emptySelectionAllowed, emptySelectionCaption); | |||
ComboBox<String> comboBox = new ComboBox<>(); | |||
comboBox.setPlaceholder(placeholder); | |||
@@ -62,6 +64,7 @@ public class ComboBoxDeclarativeTest | |||
comboBox.setPopupWidth(popupWidth); | |||
comboBox.setScrollToSelectedItem(true); | |||
comboBox.setEmptySelectionAllowed(emptySelectionAllowed); | |||
comboBox.setEmptySelectionCaption(emptySelectionCaption); | |||
testRead(design, comboBox); | |||
testWrite(design, comboBox); |
@@ -15,6 +15,8 @@ | |||
*/ | |||
package com.vaadin.tests.server.component.nativeselect; | |||
import org.junit.Test; | |||
import com.vaadin.tests.server.component.abstractsingleselect.AbstractSingleSelectDeclarativeTest; | |||
import com.vaadin.ui.NativeSelect; | |||
@@ -29,6 +31,25 @@ import com.vaadin.ui.NativeSelect; | |||
public class NativeSelectDeclarativeTest | |||
extends AbstractSingleSelectDeclarativeTest<NativeSelect> { | |||
@Test | |||
public void nativeSelectSpecificPropertiesSerialize() { | |||
boolean emptySelectionAllowed = false; | |||
String emptySelectionCaption = "foo"; | |||
String design = String.format( | |||
"<%s empty-selection-allowed='%s' " | |||
+ "empty-selection-caption='%s'/>", | |||
getComponentTag(), emptySelectionAllowed, | |||
emptySelectionCaption); | |||
NativeSelect<String> select = new NativeSelect<>(); | |||
select.setEmptySelectionAllowed(emptySelectionAllowed); | |||
select.setEmptySelectionCaption(emptySelectionCaption); | |||
testRead(design, select); | |||
testWrite(design, select); | |||
} | |||
@Override | |||
protected String getComponentTag() { | |||
return "vaadin-native-select"; |
@@ -31,6 +31,18 @@ public class NativeSelectState extends AbstractSingleSelectState { | |||
*/ | |||
public static final String STYLE_NAME = "v-select"; | |||
/** | |||
* True to allow selecting nothing (a special empty selection item is shown | |||
* at the beginning of the list), false not to allow empty selection by the | |||
* user. | |||
*/ | |||
public boolean emptySelectionAllowed = true; | |||
/** | |||
* Caption for item which represents empty selection. | |||
*/ | |||
public String emptySelectionCaption = ""; | |||
{ | |||
primaryStyleName = STYLE_NAME; | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* Copyright 2000-2016 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.nativeselect; | |||
import java.util.stream.IntStream; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Button; | |||
import com.vaadin.ui.NativeSelect; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
*/ | |||
public class NativeSelectEmptySelection extends AbstractTestUI { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
NativeSelect<String> select = new NativeSelect<>(); | |||
select.setItems(IntStream.range(1, 50) | |||
.mapToObj(index -> String.valueOf(index))); | |||
select.setEmptySelectionCaption("empty"); | |||
addComponent(select); | |||
Button update = new Button("Update Empty Caption to 'updated'", | |||
event -> select.setEmptySelectionCaption("updated")); | |||
Button disallow = new Button("Disallow empty selection item", | |||
event -> select.setEmptySelectionAllowed(false)); | |||
Button enable = new Button("Allow empty selection item", | |||
event -> select.setEmptySelectionAllowed(true)); | |||
addComponents(update, disallow, enable); | |||
} | |||
} |
@@ -11,4 +11,11 @@ public class NativeSelects | |||
protected Class<NativeSelect<Object>> getTestClass() { | |||
return (Class) NativeSelect.class; | |||
} | |||
@Override | |||
protected NativeSelect<Object> constructComponent() { | |||
NativeSelect<Object> component = super.constructComponent(); | |||
component.setEmptySelectionAllowed(false); | |||
return component; | |||
} | |||
} |
@@ -7,7 +7,7 @@ | |||
<body> | |||
<vaadin-vertical-layout> | |||
<vaadin-horizontal-layout _id="buttons" width-full></vaadin-horizontal-layout> | |||
<vaadin-native-select _id="nativeSelect"> | |||
<vaadin-native-select _id="nativeSelect" empty-selection-allowed="false"> | |||
<option item="Option 1">Foo</option> | |||
<option item="Option 2">Bar</option> | |||
<option item="Option 3">Baz</option> |
@@ -0,0 +1,71 @@ | |||
/* | |||
* Copyright 2000-2016 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.nativeselect; | |||
import java.util.Set; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import com.vaadin.testbench.TestBenchElement; | |||
import com.vaadin.testbench.elements.ButtonElement; | |||
import com.vaadin.testbench.elements.NativeSelectElement; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
/** | |||
* @author Vaadin Ltd | |||
* | |||
*/ | |||
public class NativeSelectEmptySelectionTest extends MultiBrowserTest { | |||
@Test | |||
public void checkEmptySelection() { | |||
openTestURL(); | |||
checkOptions("empty"); | |||
// change the caption | |||
$(ButtonElement.class).first().click(); | |||
checkOptions("updated"); | |||
// disable empty caption | |||
$(ButtonElement.class).get(1).click(); | |||
checkOptions(null); | |||
// enable back | |||
$(ButtonElement.class).get(2).click(); | |||
checkOptions("updated"); | |||
} | |||
private void checkOptions(String emptyCaption) { | |||
NativeSelectElement select = $(NativeSelectElement.class).first(); | |||
Set<String> originalOptions = IntStream.range(1, 50) | |||
.mapToObj(index -> String.valueOf(index)) | |||
.collect(Collectors.toSet()); | |||
Set<String> options = select.getOptions().stream() | |||
.map(TestBenchElement::getText).collect(Collectors.toSet()); | |||
if (emptyCaption == null) { | |||
Assert.assertEquals(49, options.size()); | |||
Assert.assertTrue(options.containsAll(originalOptions)); | |||
} else { | |||
options.contains(emptyCaption); | |||
Assert.assertEquals(50, options.size()); | |||
Assert.assertTrue(options.containsAll(originalOptions)); | |||
} | |||
} | |||
} |