Procházet zdrojové kódy

Add typed NativeSelect and SelectionModel functionality

Change-Id: I3d65d02c34aa6ff0281cae36cfdd8b1166292d7f
feature/vaadin8-book
Teemu Suo-Anttila před 8 roky
rodič
revize
755c893008

+ 130
- 0
client/src/main/java/com/vaadin/client/connectors/components/NativeSelectConnector.java Zobrazit soubor

@@ -0,0 +1,130 @@
/*
* 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.components;

import java.util.logging.Logger;

import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.user.client.ui.ListBox;
import com.vaadin.client.connectors.data.HasSelection;
import com.vaadin.client.data.DataChangeHandler;
import com.vaadin.client.data.DataSource;
import com.vaadin.client.data.selection.SelectionModel;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.shared.ui.Connect;
import com.vaadin.ui.components.nativeselect.NativeSelect;

import elemental.json.JsonObject;

@Connect(NativeSelect.class)
public class NativeSelectConnector extends AbstractComponentConnector implements
HasSelection {

private final class NativeSelectDataChangeHandler implements
DataChangeHandler {
@Override
public void resetDataAndSize(int estimatedNewDataSize) {
resetContent();
}

@Override
public void dataUpdated(int firstRowIndex, int numberOfRows) {
for (int i = 0; i < numberOfRows; ++i) {
int index = i + firstRowIndex;
JsonObject item = dataSource.getRow(index);
getWidget().setItemText(index, item.getString("n"));
getWidget().setValue(index, item.getString("k"));
}
}

@Override
public void dataRemoved(int firstRowIndex, int numberOfRows) {
for (int i = 0; i < numberOfRows; ++i) {
int index = i + firstRowIndex;
getWidget().removeItem(index);
}
}

@Override
public void dataAvailable(int firstRowIndex, int numberOfRows) {
// NO-OP
}

@Override
public void dataAdded(int firstRowIndex, int numberOfRows) {
if (getWidget().getItemCount() == firstRowIndex) {
for (int i = 0; i < numberOfRows; ++i) {
JsonObject item = dataSource.getRow(i + firstRowIndex);
Logger.getLogger("foo").warning(item.toJson());
getWidget().addItem(item.getString("n"),
item.getString("k"));
}
} else {
resetContent();
}
}
}

private DataSource<JsonObject> dataSource;
private SelectionModel selectionModel;

@Override
public ListBox getWidget() {
return (ListBox) super.getWidget();
}

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

getWidget().addChangeHandler(new ChangeHandler() {

@Override
public void onChange(ChangeEvent event) {
if (selectionModel == null) {
return;
}
int index = getWidget().getSelectedIndex();
JsonObject selected = dataSource.getRow(index);
selectionModel.select(selected);
}
});
}

@Override
public void setDataSource(DataSource<JsonObject> dataSource) {
if (this.dataSource != null) {
dataSource.setDataChangeHandler(null);
}
this.dataSource = dataSource;
dataSource.setDataChangeHandler(new NativeSelectDataChangeHandler());
resetContent();
}

@Override
public void setSelectionModel(SelectionModel selectionModel) {
this.selectionModel = selectionModel;
}

private void resetContent() {
getWidget().clear();
for (int i = 0; i < dataSource.size(); ++i) {
JsonObject item = dataSource.getRow(i);
getWidget().addItem(item.getString("n"), item.getString("k"));
}
}
}

+ 32
- 0
client/src/main/java/com/vaadin/client/connectors/data/HasSelection.java Zobrazit soubor

@@ -0,0 +1,32 @@
/*
* 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.data;

import com.vaadin.client.data.selection.SelectionModel;

/**
* Marker interface for Connectors that have a SelectionModel.
*/
public interface HasSelection extends HasDataSource {

/**
* Sets the selection model for this Conenctor.
*
* @param selectionModel
* selection model
*/
public void setSelectionModel(SelectionModel selectionModel);
}

+ 61
- 0
client/src/main/java/com/vaadin/client/connectors/selection/AbstractSelectionConnector.java Zobrazit soubor

@@ -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.client.connectors.selection;

import com.vaadin.client.ServerConnector;
import com.vaadin.client.connectors.data.HasSelection;
import com.vaadin.client.data.selection.SelectionModel;
import com.vaadin.client.extensions.AbstractExtensionConnector;
import com.vaadin.shared.data.typed.DataProviderConstants;

import elemental.json.JsonObject;

public abstract class AbstractSelectionConnector extends
AbstractExtensionConnector {

@Override
protected void extend(ServerConnector target) {
if (!(target instanceof HasSelection)) {
throw new UnsupportedOperationException(
"SelectionModel extending a Connector without HasDataSource");
}
// TODO: Provide SelectionModel API
// TODO: Should this use "Registration" approach for easy and safe
// removal?
((HasSelection) target).setSelectionModel(createSelectionModel());
}

/**
* Creates a selection model object to be used by the Connector.
*
* @return created selection model
*/
protected abstract SelectionModel createSelectionModel();

protected static String getKey(JsonObject item) {
return item.getString(DataProviderConstants.KEY);
}

protected static boolean jsonEquals(JsonObject a, JsonObject b) {
final String key = DataProviderConstants.KEY;
if (a != null && b != null) {
if (a.hasKey(key) && b.hasKey(key)) {
return a.getString(key).equals(b.getString(key));
}
}
return a == b;
}
}

+ 55
- 0
client/src/main/java/com/vaadin/client/connectors/selection/NullSelectionConnector.java Zobrazit soubor

@@ -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.client.connectors.selection;

import com.vaadin.client.ServerConnector;
import com.vaadin.client.connectors.data.HasSelection;
import com.vaadin.client.data.selection.SelectionModel;
import com.vaadin.client.data.selection.SelectionModel.Single;
import com.vaadin.server.communication.data.typed.SelectionModel.NullSelection;
import com.vaadin.shared.ui.Connect;

import elemental.json.JsonObject;

@Connect(NullSelection.class)
public class NullSelectionConnector extends AbstractSelectionConnector {

@Override
protected void extend(ServerConnector target) {
if (target instanceof HasSelection) {
super.extend(target);
}
}

@Override
protected SelectionModel createSelectionModel() {
return new SelectionModel() {

@Override
public void select(JsonObject item) {
}

@Override
public void deselect(JsonObject item) {
}

@Override
public boolean isSelected(JsonObject item) {
return false;
}
};
}
}

+ 69
- 0
client/src/main/java/com/vaadin/client/connectors/selection/SingleSelectionConnector.java Zobrazit soubor

@@ -0,0 +1,69 @@
/*
* 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.selection;

import com.vaadin.client.data.selection.SelectionModel;
import com.vaadin.client.data.selection.SelectionModel.Single;
import com.vaadin.shared.data.selection.SelectionServerRpc;
import com.vaadin.shared.ui.Connect;

import elemental.json.JsonObject;

@Connect(com.vaadin.server.communication.data.typed.SingleSelection.class)
public class SingleSelectionConnector extends AbstractSelectionConnector {

public static class SingleSelection implements SelectionModel, Single {

private JsonObject value;
private SelectionServerRpc rpc;

public SingleSelection(SelectionServerRpc rpc) {
this.rpc = rpc;
}

@Override
public void select(JsonObject item) {
if (item != null && !jsonEquals(value, item)) {
rpc.select(getKey(item));
value = item;
}
}

@Override
public void deselect(JsonObject item) {
if (item != null && jsonEquals(value, item)) {
rpc.deselect(getKey(item));
value = null;
}
}

@Override
public boolean isSelected(JsonObject item) {
return jsonEquals(value, item);
}
}

@Override
protected SelectionModel createSelectionModel() {
return new SingleSelection(getRpcProxy(SelectionServerRpc.class));
}

@Override
public void onUnregister() {
super.onUnregister();
}

}

+ 45
- 0
client/src/main/java/com/vaadin/client/data/selection/SelectionModel.java Zobrazit soubor

@@ -0,0 +1,45 @@
/*
* 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.data.selection;

import elemental.json.JsonObject;

public interface SelectionModel {

public interface Single {
}

public interface Multi {
}

/**
* Selects the given item. Some selection models will deselect items if
* needed.
*/
void select(JsonObject item);

/**
* Deselects the given item. Some selection model can prevent this.
*/
void deselect(JsonObject item);

/**
* Gets the current state of selection for given item.
*
* @return {@code true} if selected; {@code false} if not
*/
boolean isSelected(JsonObject item);
}

+ 67
- 0
server/src/main/java/com/vaadin/server/communication/data/typed/AbstractSelectionModel.java Zobrazit soubor

@@ -0,0 +1,67 @@
/*
* 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.server.communication.data.typed;

import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.AbstractExtension;
import com.vaadin.server.Extension;
import com.vaadin.ui.Component;
import com.vaadin.ui.components.Listing;

/**
* Abstract base class for {@link SelectionModel}s.
*
* @param <T>
* type of selected data
*/
public abstract class AbstractSelectionModel<T> extends AbstractExtension
implements SelectionModel<T> {

private Listing<T> parent;

@Override
public void setParentListing(Listing<T> target) {
if (target instanceof Component && target instanceof Listing
&& target instanceof AbstractClientConnector) {
super.extend((AbstractClientConnector) target);
parent = target;
} else {
throw new IllegalArgumentException(
"Extended object was not suitable for a SelectionModel");
}
}

protected final Listing<T> getParentListing() {
return parent;
}

protected final DataKeyMapper<T> getKeyMapper() {
for (Extension e : ((Component) parent).getExtensions()) {
if (e instanceof DataProvider) {
return ((DataProvider<T>) e).getKeyMapper();
}
}
return null;
}

protected final T getData(String key) {
DataKeyMapper<T> keyMapper = getKeyMapper();
if (keyMapper != null) {
return keyMapper.get(key);
}
return null;
}
}

+ 19
- 3
server/src/main/java/com/vaadin/server/communication/data/typed/SelectionModel.java Zobrazit soubor

@@ -22,7 +22,10 @@ import java.util.List;

import com.vaadin.event.handler.Handler;
import com.vaadin.event.handler.Registration;
import com.vaadin.server.Extension;
import com.vaadin.ui.Component;
import com.vaadin.ui.components.HasValue;
import com.vaadin.ui.components.Listing;

/**
* Generic selection model interface.
@@ -31,7 +34,7 @@ import com.vaadin.ui.components.HasValue;
* @param <T>
* type of selected values
*/
public interface SelectionModel<T> extends Serializable {
public interface SelectionModel<T> extends Serializable, Extension {

/**
* Selection model for selection a single value.
@@ -59,14 +62,27 @@ public interface SelectionModel<T> extends Serializable {
*/
Collection<T> getSelected();

/**
* Add this extension to the target listing component. SelectionModel can
* only extend {@link Component}s that implement {@link Listing} interface.
* This method should only be called from a listing component when changing
* the selection model.
*
* @param target
* listing component to extend.
*
* @throws IllegalArgumentException
*/
void setParentListing(Listing<T> target);

/**
* Dummy selection model.
*
* @param <T>
* selected data type
*/
public static class NullSelectionModel<T> implements
SelectionModel.Single<T> {
public static class NullSelection<T> extends AbstractSelectionModel<T>
implements SelectionModel.Single<T> {

@Override
public void setValue(T value) {

+ 82
- 0
server/src/main/java/com/vaadin/server/communication/data/typed/SingleSelection.java Zobrazit soubor

@@ -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.server.communication.data.typed;

import java.util.Collection;
import java.util.Collections;

import com.vaadin.event.handler.Handler;
import com.vaadin.event.handler.Registration;
import com.vaadin.server.communication.data.typed.SelectionModel.Single;
import com.vaadin.shared.data.selection.SelectionServerRpc;

/**
* {@link SelectionModel} for selecting a single value.
*
* @param <T>
* type of selected data
*/
public class SingleSelection<T> extends AbstractSelectionModel<T> implements
Single<T> {

public SingleSelection() {
registerRpc(new SelectionServerRpc() {

@Override
public void select(String key) {
setValue(getData(key));
}

@Override
public void deselect(String key) {
if (getData(key).equals(value)) {
setValue(null);
}
}
});
}

private T value = null;

@Override
public Collection<T> getSelected() {
if (value != null) {
return Collections.singleton(value);
} else {
return Collections.emptySet();
}
}

@Override
public void setValue(T value) {
if (this.value != value) {
this.value = value;
// TODO: fire event
}
}

@Override
public T getValue() {
return value;
}

@Override
public Registration onChange(Handler<T> handler) {
return () -> {
// TODO: Event registration
};
}
}

+ 98
- 0
server/src/main/java/com/vaadin/ui/components/nativeselect/NativeSelect.java Zobrazit soubor

@@ -0,0 +1,98 @@
/*
* 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.components.nativeselect;

import java.util.function.Function;

import com.vaadin.server.communication.data.typed.DataProvider;
import com.vaadin.server.communication.data.typed.DataSource;
import com.vaadin.server.communication.data.typed.SelectionModel;
import com.vaadin.server.communication.data.typed.SingleSelection;
import com.vaadin.server.communication.data.typed.TypedDataGenerator;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.components.Listing;

import elemental.json.JsonObject;

public class NativeSelect<T> extends AbstractComponent implements Listing<T> {

private DataSource<T> dataSource;
private DataProvider<T> dataProvider;
private SelectionModel<T> selectionModel;
private Function<T, String> nameProvider = T::toString;

public NativeSelect() {
internalSetSelectionModel(new SingleSelection<>());
}

public NativeSelect(DataSource<T> dataSource) {
this();
internalSetDataSource(dataSource);
}

@Override
public void setDataSource(DataSource<T> data) {
internalSetDataSource(data);
}

private void internalSetDataSource(DataSource<T> data) {
if (dataProvider != null) {
dataProvider.remove();
dataProvider = null;
}
dataSource = data;
if (dataSource != null) {
dataProvider = DataProvider.create(dataSource, this);
dataProvider.addDataGenerator(new TypedDataGenerator<T>() {

@Override
public void generateData(T data, JsonObject jsonObject) {
jsonObject.put("n", nameProvider.apply(data));
}

@Override
public void destroyData(T data) {
}
});
}
}

@Override
public DataSource<T> getDataSource() {
return dataSource;
}

@Override
public SelectionModel<T> getSelectionModel() {
return selectionModel;
}

@Override
public void setSelectionModel(SelectionModel<T> model) {
internalSetSelectionModel(model);
}

private void internalSetSelectionModel(SelectionModel<T> model) {
if (selectionModel != null) {
selectionModel.remove();
}
selectionModel = model;
if (model != null) {
model.setParentListing(this);
}
}

}

+ 40
- 0
shared/src/main/java/com/vaadin/shared/data/selection/SelectionServerRpc.java Zobrazit soubor

@@ -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.shared.data.selection;

import com.vaadin.shared.communication.ServerRpc;

/**
* Simple API for SelectionModel client to server communication
*/
public interface SelectionServerRpc extends ServerRpc {

/**
* Select an item based on it's key.
*
* @param key
* key of item
*/
void select(String key);

/**
* Deselect an item based on it's key.
*
* @param key
* key of item
*/
void deselect(String key);
}

+ 48
- 0
uitest/src/main/java/com/vaadin/tests/databinding/ListingTestUI.java Zobrazit soubor

@@ -0,0 +1,48 @@
package com.vaadin.tests.databinding;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;

import com.vaadin.server.VaadinRequest;
import com.vaadin.server.communication.data.typed.AbstractDataSource;
import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.ui.Button;
import com.vaadin.ui.Notification;
import com.vaadin.ui.components.nativeselect.NativeSelect;

public class ListingTestUI extends AbstractTestUI {

@Override
protected void setup(VaadinRequest request) {
final List<String> options = createOptions();
NativeSelect<String> select = new NativeSelect<>(
new AbstractDataSource<String>() {

@Override
public void save(String data) {
}

@Override
public void remove(String data) {
}

@Override
public Iterator<String> iterator() {
return options.iterator();
}
});
addComponent(select);

addComponent(new Button("Notify", e -> select.getSelectionModel()
.getSelected().forEach(s -> Notification.show(s))));
}

private List<String> createOptions() {
List<String> options = new ArrayList<>();
Stream.of(1, 2, 3, 4, 5).map(i -> "Option " + i).forEach(options::add);
return options;
}

}

Načítá se…
Zrušit
Uložit