Java tutorial
/* * Copyright 2011 JBoss, by Red Hat, Inc * * 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.ui.client.widget; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.jboss.errai.common.client.api.Assert; import org.jboss.errai.databinding.client.BindableListChangeHandler; import org.jboss.errai.databinding.client.BindableListWrapper; import org.jboss.errai.ioc.client.container.IOC; import org.jboss.errai.ioc.client.container.SyncToAsyncBeanManagerAdpater; import org.jboss.errai.ioc.client.container.async.AsyncBeanDef; import org.jboss.errai.ioc.client.container.async.AsyncBeanManager; import org.jboss.errai.ioc.client.container.async.CreationalCallback; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasValue; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; /** * A type of widget that displays and manages a child widget for each item in a list of model * objects. The widget instances are managed by Errai's IOC container and are arranged in a * {@link ComplexPanel}. By default, a {@link VerticalPanel} is used, but an alternative can be * specified using {@link #ListWidget(ComplexPanel)}. * * @param <M> * the model type * @param <W> * the item widget type, needs to implement {@link HasModel} for associating the widget * instance with the corresponding model instance. * * @author Christian Sadilek <csadilek@redhat.com> */ public abstract class ListWidget<M, W extends HasModel<M> & IsWidget> extends Composite implements HasValue<List<M>>, BindableListChangeHandler<M> { private final ComplexPanel panel; private BindableListWrapper<M> items; private final List<WidgetCreationalCallback> callbacks = new LinkedList<WidgetCreationalCallback>(); private int pendingCallbacks; private boolean valueChangeHandlerInitialized; protected ListWidget() { this(new VerticalPanel()); } protected ListWidget(ComplexPanel panel) { this.panel = Assert.notNull(panel); initWidget(panel); } /** * Returns the class object for the item widget type <W> to look up new instances of the widget * using the client-side bean manager. * * @return the item widget type. */ protected abstract Class<W> getItemWidgetType(); /** * Called after all item widgets have been rendered. By default, this is a NOOP, but subclasses * can add behaviour if needed. * <p> * Using the standard synchronous bean manager this method is invoked before * {@link #setItems(List)} returns. However, when using the asynchronous bean manager and * declaring @LoadAsync on the item widget, this method might be called after * {@link #setItems(List)} returns and after the corresponding JavaScript code has been * downloaded. * * @param items * the rendered item list. Every change to this list will update the corresponding * rendered item widgets. */ protected void onItemsRendered(List<M> items) { }; /** * Returns the panel that contains all item widgets. * * @return the item widget panel, never null. */ protected ComplexPanel getPanel() { return panel; } /** * Sets the list of model objects. A widget instance of type <W> will be added to the panel for * each object in the list. The list will be wrapped in an {@link BindableListWrapper} to make * direct changes to the list observable. * <p> * If the standard synchronous bean manager is used it is guaranteed that all widgets have been * added to the panel when this method returns. In case the asynchronous bean manager is used this * method might return before the widgets have been added to the panel. See * {@link #onItemsRendered()}. * * @param items * The list of model objects. If null or empty all existing child widgets will be * removed. */ public void setItems(List<M> items) { boolean changed = this.items != items; if (items instanceof BindableListWrapper) { this.items = (BindableListWrapper<M>) items; } else { this.items = new BindableListWrapper<M>(items); } if (changed) { this.items.addChangeHandler(this); } init(); } private void init() { // The AsyncBeanManager API works in both synchronous and asynchronous IOC mode AsyncBeanManager bm = IOC.getAsyncBeanManager(); // In the case that this method is executed before the first call has successfully processed all // of its callbacks, we must cancel those uncompleted callbacks in flight to prevent duplicate // data in the ListWidget. for (WidgetCreationalCallback callback : callbacks) { callback.discard(); } callbacks.clear(); pendingCallbacks = 0; // clean up the old widgets before we add new ones (this will eventually become a feature of the // framework: ERRAI-375) Iterator<Widget> it = panel.iterator(); while (it.hasNext()) { bm.destroyBean(it.next()); it.remove(); } if (items == null) return; pendingCallbacks = items.size(); AsyncBeanDef<W> itemBeanDef = bm.lookupBean(getItemWidgetType()); for (final M item : items) { final WidgetCreationalCallback callback = new WidgetCreationalCallback(item); callbacks.add(callback); itemBeanDef.newInstance(callback); } } /** * Returns the widget at the specified index. * * @param index * the index to be retrieved * * @return the widget at the specified index * * @throws IndexOutOfBoundsException * if the index is out of range */ @SuppressWarnings("unchecked") public W getWidget(int index) { return (W) panel.getWidget(index); } /** * Returns the widget currently displaying the provided model. * * @param model * the model displayed by the widget * * @return the widget displaying the provided model instance, null if no widget was found for the model. */ public W getWidget(M model) { int index = items.indexOf(model); return getWidget(index); } @Override public HandlerRegistration addValueChangeHandler(ValueChangeHandler<List<M>> handler) { if (!valueChangeHandlerInitialized) { valueChangeHandlerInitialized = true; addDomHandler(new ChangeHandler() { @Override public void onChange(ChangeEvent event) { ValueChangeEvent.fire(ListWidget.this, getValue()); } }, ChangeEvent.getType()); } return addHandler(handler, ValueChangeEvent.getType()); } @Override public List<M> getValue() { if (items == null) { items = new BindableListWrapper<M>(new ArrayList<M>()); items.addChangeHandler(this); } return items; } @Override public void setValue(List<M> value) { setValue(value, false); } @Override public void setValue(List<M> value, boolean fireEvents) { List<M> oldValue = getValue(); setItems(value); if (fireEvents) { ValueChangeEvent.fireIfNotEqual(this, oldValue, value); } } /** * A callback invoked by the {@link AsyncBeanManager} or {@link SyncToAsyncBeanManagerAdpater} * when the widget instance was created. It will associate the corresponding model instance with * the widget and add the widget to the panel. */ private class WidgetCreationalCallback implements CreationalCallback<W> { private boolean discard; private final M item; private WidgetCreationalCallback(M item) { this.item = item; } @Override public void callback(W widget) { if (!discard) { widget.setModel(item); panel.add(widget); if (--pendingCallbacks == 0) { onItemsRendered(items); } } } public void discard() { this.discard = true; } } @Override public void onItemAdded(List<M> oldList, M item) { addWidget(item); } @Override public void onItemAddedAt(List<M> oldList, int index, M item) { for (int i = index; i < items.size(); i++) { addAndReplaceWidget(index, i); } } @Override public void onItemsAdded(List<M> oldList, Collection<? extends M> items) { for (M m : items) { addWidget(m); } } @Override public void onItemsAddedAt(List<M> oldList, int index, Collection<? extends M> item) { for (int i = index; i < items.size(); i++) { addAndReplaceWidget(index, i); } } @Override public void onItemsCleared(List<M> oldList) { panel.clear(); } @Override public void onItemRemovedAt(List<M> oldList, int index) { panel.remove(index); } @Override public void onItemsRemovedAt(List<M> oldList, List<Integer> indexes) { for (Integer index : indexes) { panel.remove(index); } } @Override public void onItemChanged(List<M> oldList, int index, M item) { for (int i = index; i < items.size(); i++) { addAndReplaceWidget(index, i); } } private void addAndReplaceWidget(final int startIndex, final int index) { if (index < panel.getWidgetCount()) { panel.remove(startIndex); } addWidget(items.get(index)); } private void addWidget(final M m) { // This call is always synchronous, since the list can only be manipulated after // onItemsRendered was called. At that point the code of a potential split point must have // already been downloaded. AsyncBeanDef<W> itemBeanDef = IOC.getAsyncBeanManager().lookupBean(getItemWidgetType()); itemBeanDef.getInstance(new CreationalCallback<W>() { @Override public void callback(W widget) { widget.setModel(m); panel.add(widget); } }); } }