/**
* 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.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.jboss.errai.common.client.api.Assert;
import org.jboss.errai.common.client.dom.HTMLElement;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.TakesValue;
/**
* The default implementation of a {@link ListComponent}. Accepts as argument functions for creating and destorying UI
* components, as well as accessing the DOM elements of a UI component, allowing it to be used with or independently of
* the Errai IoC container.
*
* @author Max Barkley <mbarkley@redhat.com>
*/
public class DefaultListComponent<M, C extends TakesValue<M>> implements ListComponent<M, C> {
private final Collection<Consumer<C>> creationHandlers = new ArrayList<>();
private final Collection<Consumer<C>> destructionHandlers = new ArrayList<>();
private final HTMLElement root;
private final Supplier<C> supplier;
private final Consumer<C> destroyer;
private final Function<C, HTMLElement> elementAccessor;
private final List<C> components = new ArrayList<>();
private List<M> value;
private Consumer<C> selector = c -> {};
private Consumer<C> deselector = c -> {};
private final Set<C> selected = Collections.newSetFromMap(new IdentityHashMap<>());
public DefaultListComponent(final HTMLElement root, final Supplier<C> supplier, final Consumer<C> destroyer, final Function<C, HTMLElement> elementAccessor) {
this.root = root;
this.supplier = supplier;
this.destroyer = destroyer;
this.elementAccessor = elementAccessor;
}
@Override
public HandlerRegistration addComponentCreationHandler(final Consumer<C> handler) {
creationHandlers.add(handler);
return () -> creationHandlers.remove(handler);
}
@Override
public HandlerRegistration addComponentDestructionHandler(final Consumer<C> handler) {
destructionHandlers.add(handler);
return () -> destructionHandlers.remove(handler);
}
@Override
public HTMLElement getElement() {
return root;
}
@Override
public void setValue(final List<M> value) {
final boolean changed = this.value != value;
this.value = value;
if (changed) {
for (int i = components.size()-1; i > -1; i--) {
removeComponent(i);
}
for (int i = 0; i < this.value.size(); i++) {
addComponent(i, this.value.get(i));
}
}
}
@Override
public List<M> getValue() {
return value;
}
@Override
public void onItemAdded(final List<M> source, final M item) {
addComponent(components.size(), item);
}
@Override
public void onItemAddedAt(final List<M> source, final int index, final M item) {
addComponent(index, item);
}
@Override
public void onItemsAdded(final List<M> source, final Collection<? extends M> items) {
for (final M model : items) {
addComponent(components.size(), model);
}
}
@Override
public void onItemsAddedAt(final List<M> source, final int index, final Collection<? extends M> items) {
int i = index;
for (final M model : items) {
addComponent(i++, model);
}
}
@Override
public void onItemRemovedAt(final List<M> source, final int index) {
removeComponent(index);
}
@Override
public void onItemsRemovedAt(final List<M> source, final List<Integer> indexes) {
Collections.sort(indexes, (n,m) -> m - n);
for (final int index : indexes) {
removeComponent(index);
}
}
@Override
public void onItemsCleared(final List<M> source) {
for (int i = components.size()-1; i >= 0; i--) {
removeComponent(i);
}
}
@Override
public void onItemChanged(final List<M> source, final int index, final M item) {
components.get(index).setValue(item);
}
@Override
public C getComponent(final int index) {
return components.get(index);
}
private C createComponent(final M model) {
final C component = supplier.get();
component.setValue(model);
return component;
}
private void removeComponent(final int index) {
final C component = components.remove(index);
for (final Consumer<C> handler : destructionHandlers) {
handler.accept(component);
}
final HTMLElement element = elementAccessor.apply(component);
element.getParentNode().removeChild(element);
destroyer.accept(component);
}
private void addComponent(final int index, final M item) {
final C component = createComponent(item);
final HTMLElement element = Assert.notNull(elementAccessor.apply(component));
if (index < components.size()) {
root.insertBefore(element, Assert.notNull(elementAccessor.apply(components.get(index))));
}
else {
root.appendChild(element);
}
components.add(index, component);
for (final Consumer<C> handler : creationHandlers) {
handler.accept(component);
}
}
@Override
public void setSelector(final Consumer<C> selector) {
this.selector = Assert.notNull(selector);
}
@Override
public void setDeselector(final Consumer<C> deselector) {
this.deselector = Assert.notNull(deselector);
}
@Override
public void selectComponents(final Collection<C> components) {
for (final C comp : components) {
selected.add(comp);
selector.accept(comp);
}
}
@Override
public Collection<C> getSelectedComponents() {
return Collections.unmodifiableCollection(selected);
}
@Override
public void deselectComponents(final Collection<C> components) {
for (final C comp : components) {
if (selected.remove(comp)) {
deselector.accept(comp);
}
}
}
}