/**
* Copyright (C) 2016 Red Hat, Inc. and/or its affiliates.
*
* 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 org.jboss.errai.databinding.client.components;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jboss.errai.common.client.api.IsElement;
import org.jboss.errai.common.client.dom.HTMLElement;
import org.jboss.errai.databinding.client.api.Bindable;
import org.jboss.errai.databinding.client.api.handler.list.BindableListChangeHandler;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.TableSectionElement;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.ui.IsWidget;
/**
* A component that binds a list of {@link Bindable} models to a displayed list of UI components. Supports selection of
* displayed components.
*
* Instances can be obtained via {@link #forIsElementComponent(Supplier, Consumer)} and
* {@link #forIsWidgetComponent(Supplier, Consumer)}, or by injection with Errai IoC.
*
* When injecting a {@link ListComponent}, individual displayed components will be looked up via Errai IoC. This lookup
* will use any qualifiers on the {@link ListComponent} injection site (with the exception of the {@link ListContainer}
* qualifier explained below).
*
* The type of element used to contain list items can be specified with the {@link ListContainer} qualifier. The
* {@link ListContainer#value()} will be the tag name of the element used.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public interface ListComponent<M, C extends TakesValue<M>> extends IsElement, TakesValue<List<M>>, BindableListChangeHandler<M> {
/**
* @param index
* A valid index for a model in the list of models.
* @return The UI component displaying the model at the given index in the list of models. Never null.
* @throws IndexOutOfBoundsException
* If the given index is invalid.
*/
C getComponent(int index);
/**
* @param model
* Never null.
* @return An optional containing a UI component associated with the given model, iff the given model is displayed by
* this {@link ListComponent}. Otherwise {@link Optional#empty()}.
*/
default Optional<C> getComponent(final M model) {
final int index = getValue().indexOf(model);
if (index == -1) {
return Optional.empty();
}
else {
return Optional.ofNullable(getComponent(index));
}
}
/**
* @param handler
* A handler that is called for every new UI component created for this list.
* @return A {@link HandlerRegistration} for removing the added handler.
*/
HandlerRegistration addComponentCreationHandler(Consumer<C> handler);
/**
* @param handler
* A handler that is called for every UI component removed for this list.
* @return A {@link HandlerRegistration} for removing the added handler.
*/
HandlerRegistration addComponentDestructionHandler(Consumer<C> handler);
/**
* @param selector
* A {@link Consumer} called for every component that is selected via {@link #selectComponent(TakesValue)} or
* {@link #selectComponents(Collection)}.
*/
void setSelector(Consumer<C> selector);
/**
* @param selector
* A {@link Consumer} called for every component that is unselected via
* {@link #deselectComponent(TakesValue)}, {@link #deselectComponents(Collection)}, or {@link #deselectAll()}
* .
*/
void setDeselector(Consumer<C> deselector);
/**
* A convenience method for selecting components via their respective models. This is equivalent to calling
* {@link #getComponent(Object)} for each model and then calling {@link #selectComponents(Collection)} on the
* resulting collection.
*
* @param models
* A collection of models whose respective components will be selected.
*/
default void selectModels(final Collection<M> models) {
for (final M model: models) {
getComponent(model).ifPresent(c -> selectComponent(c));
}
}
/**
* A convenience method for selecting a component via its respective model. This is equivalent to calling
* {@code selectComponent(getComponent(model))}.
*
* @param model
* A model whose respective component is to be selected.
*/
default void selectModel(final M model) {
selectModels(Collections.singleton(model));
}
/**
* @param components
* A collection of UI components in this list to be selected. After this call, every component in this list
* will be part of the collection returned by {@link #getSelectedComponents()}. If a selector has been set
* via {@link #setSelector(Consumer)} it will be called for each of the components.
*/
void selectComponents(Collection<C> components);
/**
* @param component
* A UI components in this list to be selected. After this call the component will be part of the collection
* returned by {@link #getSelectedComponents()}. If a selector has been set via
* {@link #setSelector(Consumer)} it will be called for this component.
*/
default void selectComponent(final C component) {
selectComponents(Collections.singleton(component));
}
/**
* @return A collection of the currently selected components. Components can be added to this collection via the
* {@code select*} methods and removed via the {@code deselect*} methods.
*/
Collection<C> getSelectedComponents();
/**
* A convenience method for getting all of the models for the selected components.
*
* @return A collection of models where for each model, the component in this list bound to that model is part of the
* collection returned by {@link #getSelectedComponents()}.
*/
default Collection<M> getSelectedModels() {
final Collection<C> components = getSelectedComponents();
final Collection<M> models = new ArrayList<>(components.size());
for (final C comp : components) {
models.add(comp.getValue());
}
return models;
}
/**
* @param components
* A collection of UI components in this list to be unselected. After this call, every component in this list
* will not be part of the collection returned by {@link #getSelectedComponents()}. If a deselector has been
* set via {@link #setDeselector(Consumer)} it will be called for each of the components that was previously
* selected.
*/
void deselectComponents(Collection<C> components);
/**
* @param component
* A UI component in this list to be unselected. After this call the component will not be part of the
* collection returned by {@link #getSelectedComponents()}. If a deselector has been set via
* {@link #setDeselector(Consumer)} it will be called for this component iff the component was previously
* selected.
*/
default void deselectComponent(final C component) {
deselectComponents(Collections.singleton(component));
}
/**
* A convenience method for deselecting components by their respective models.
*
* @param models
* A collection of models. After this call, any component having one of these models will be deslected as if
* {@link #deselectComponent(TakesValue)} were called for it.
*/
default void deselectModels(final Collection<M> models) {
for (final M model : models) {
getComponent(model).ifPresent(c -> deselectComponent(c));
}
}
/**
* A convenience method for deselecting a component by its respective model.
*
* @param model
* A model that, after this call, the component having this model will be deslected as if
* {@link #deselectComponent(TakesValue)} were called for it.
*/
default void deselectModel(final M model) {
deselectModels(Collections.singleton(model));
}
/**
* A convenience method for deselcting all currently selected components. Equivalent to
* {@code deselectComponents(getSelectedComponenets)}.
*/
default void deselectAll() {
deselectComponents(getSelectedComponents());
}
/**
* @param supplier
* Supplies new UI components for displaying models added to the value of this {@link ListComponent}.
* @param destroyer
* Performs any required clean-up for UI components after their respective model has been remove from the
* value of this {@link ListComponent}.
* @return A {@link Builder} for a {@link ListComponent} displaying UI components which implement {@link IsElement}.
*/
static <M, C extends TakesValue<M> & IsElement> Builder<M, C> forIsElementComponent(final Supplier<C> supplier,
final Consumer<C> destroyer) {
return new Builder<>(root -> new DefaultListComponent<>(root, supplier, destroyer, c -> c.getElement()));
}
/**
* @param supplier
* Supplies new UI components for displaying models added to the value of this {@link ListComponent}.
* @param destroyer
* Performs any required clean-up for UI components after their respective model has been remove from the
* value of this {@link ListComponent}.
* @return A {@link Builder} for a {@link ListComponent} displaying UI components which implement {@link IsWidget}.
*/
static <M, C extends TakesValue<M> & IsWidget> Builder<M, C> forIsWidgetComponent(final Supplier<C> supplier,
final Consumer<C> destroyer) {
return new Builder<>(root -> new DefaultListComponent<>(root, supplier, destroyer, c -> (HTMLElement) c.asWidget().getElement()));
}
/**
* Allows for building {@link ListComponent ListComponents} with different kinds of container elements.
*/
static class Builder<M, C extends TakesValue<M>> {
private final Function<HTMLElement, ListComponent<M, C>> factory;
private Builder(final Function<HTMLElement, ListComponent<M, C>> factory) {
this.factory = factory;
}
/**
* @param tagName
* The tag name of a DOM element.
* @return A list component that displays UI components for individual models in an element with the given tag name.
*/
public ListComponent<M, C> inElement(final String tagName) {
return factory.apply((HTMLElement) Document.get().createElement(tagName));
}
/**
* @return A list component that displays UI components for individual models in a {@code div} tag.
*/
public ListComponent<M, C> inDiv() {
return inElement(DivElement.TAG);
}
/**
* @return A list component that displays UI components for individual models in a {@code tbody} tag.
*/
public ListComponent<M, C> inTBody() {
return inElement(TableSectionElement.TAG_TBODY);
}
}
}