com.google.gwt.user.cellview.client.CellList.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.user.cellview.client.CellList.java

Source

/*
 * Copyright 2010 Google 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 com.google.gwt.user.cellview.client;

import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.resources.client.ImageResource.ImageOptions;
import com.google.gwt.resources.client.ImageResource.RepeatStyle;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.AttachDetachException;
import com.google.gwt.user.client.ui.DeckPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.CellPreviewEvent;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionModel;

import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * A single column list of cells.
 * 
 * <p>
 * <h3>Examples</h3>
 * <dl>
 * <dt>Trivial example</dt>
 * <dd>{@example com.google.gwt.examples.cellview.CellListExample}</dd>
 * <dt>Handling user input with ValueUpdater</dt>
 * <dd>{@example com.google.gwt.examples.cellview.CellListValueUpdaterExample}</dd>
 * <dt>Pushing data with List Data Provider (backed by {@link List})</dt>
 * <dd>{@example com.google.gwt.examples.view.ListDataProviderExample}</dd>
 * <dt>Pushing data asynchronously with Async Data Provider</dt>
 * <dd>{@example com.google.gwt.examples.view.AsyncDataProviderExample}</dd>
 * <dt>Writing a custom data provider</dt>
 * <dd>{@example com.google.gwt.examples.view.RangeChangeHandlerExample}</dd>
 * <dt>Using a key provider to track objects as they change</dt>
 * <dd>{@example com.google.gwt.examples.view.KeyProviderExample}</dd>
 * </dl>
 * </p>
 * 
 * @param <T> the data type of list items
 */
public class CellList<T> extends AbstractHasData<T> {

    /**
     * A ClientBundle that provides images for this widget.
     */
    public interface Resources extends ClientBundle {
        /**
         * The background used for selected items.
         */
        @ImageOptions(repeatStyle = RepeatStyle.Horizontal, flipRtl = true)
        ImageResource cellListSelectedBackground();

        /**
         * The styles used in this widget.
         */
        @Source(Style.DEFAULT_CSS)
        Style cellListStyle();
    }

    /**
     * Styles used by this widget.
     */
    @ImportedWithPrefix("gwt-CellList")
    public interface Style extends CssResource {
        /**
         * The path to the default CSS styles used by this resource.
         */
        String DEFAULT_CSS = "com/google/gwt/user/cellview/client/CellList.css";

        /**
         * Applied to even items.
         */
        String cellListEvenItem();

        /**
         * Applied to the keyboard selected item.
         */
        String cellListKeyboardSelectedItem();

        /**
         * Applied to odd items.
         */
        String cellListOddItem();

        /**
         * Applied to selected items.
         */
        String cellListSelectedItem();

        /**
         * Applied to the widget.
         */
        String cellListWidget();
    }

    interface Template extends SafeHtmlTemplates {
        @Template("<div onclick=\"\" __idx=\"{0}\" class=\"{1}\" style=\"outline:none;\" >{2}</div>")
        SafeHtml div(int idx, String classes, SafeHtml cellContents);
    }

    /**
     * The default page size.
     */
    private static final int DEFAULT_PAGE_SIZE = 25;

    private static Resources DEFAULT_RESOURCES;

    private static final Template TEMPLATE = GWT.create(Template.class);

    private static Resources getDefaultResources() {
        if (DEFAULT_RESOURCES == null) {
            DEFAULT_RESOURCES = GWT.create(Resources.class);
        }
        return DEFAULT_RESOURCES;
    }

    private final Cell<T> cell;
    private boolean cellIsEditing;
    private final Element childContainer;
    private SafeHtml emptyListMessage = SafeHtmlUtils.fromSafeConstant("");
    private final SimplePanel emptyListWidgetContainer = new SimplePanel();
    private final SimplePanel loadingIndicatorContainer = new SimplePanel();

    /**
     * A {@link DeckPanel} to hold widgets associated with various loading states.
     */
    private final DeckPanel messagesPanel = new DeckPanel();

    private final Style style;

    private ValueUpdater<T> valueUpdater;

    /**
     * Construct a new {@link CellList}.
     * 
     * @param cell the cell used to render each item
     */
    public CellList(final Cell<T> cell) {
        this(cell, getDefaultResources(), null);
    }

    /**
     * Construct a new {@link CellList} with the specified {@link Resources}.
     * 
     * @param cell the cell used to render each item
     * @param resources the resources used for this widget
     */
    public CellList(final Cell<T> cell, Resources resources) {
        this(cell, resources, null);
    }

    /**
     * Construct a new {@link CellList} with the specified {@link ProvidesKey key
     * provider}.
     * 
     * @param cell the cell used to render each item
     * @param keyProvider an instance of ProvidesKey<T>, or null if the record
     *          object should act as its own key
     */
    public CellList(final Cell<T> cell, ProvidesKey<T> keyProvider) {
        this(cell, getDefaultResources(), keyProvider);
    }

    /**
     * Construct a new {@link CellList} with the specified {@link Resources} and
     * {@link ProvidesKey key provider}.
     * 
     * @param cell the cell used to render each item
     * @param resources the resources used for this widget
     * @param keyProvider an instance of ProvidesKey<T>, or null if the record
     *          object should act as its own key
     */
    public CellList(final Cell<T> cell, Resources resources, ProvidesKey<T> keyProvider) {
        super(Document.get().createDivElement(), DEFAULT_PAGE_SIZE, keyProvider);
        this.cell = cell;
        this.style = resources.cellListStyle();
        this.style.ensureInjected();

        String widgetStyle = this.style.cellListWidget();
        if (widgetStyle != null) {
            // The widget style is null when used in CellBrowser.
            addStyleName(widgetStyle);
        }

        // Add the child container.
        childContainer = Document.get().createDivElement();
        DivElement outerDiv = getElement().cast();
        outerDiv.appendChild(childContainer);

        // Attach the message panel.
        outerDiv.appendChild(messagesPanel.getElement());
        adopt(messagesPanel);
        messagesPanel.add(emptyListWidgetContainer);
        messagesPanel.add(loadingIndicatorContainer);

        // Sink events that the cell consumes.
        CellBasedWidgetImpl.get().sinkEvents(this, cell.getConsumedEvents());
    }

    /**
     * Get the message that is displayed when there is no data.
     * 
     * @return the empty message
     * @see #setEmptyListMessage(SafeHtml)
     * @deprecated as of GWT 2.3, use {@link #getEmptyListWidget()} instead
     */
    @Deprecated
    public SafeHtml getEmptyListMessage() {
        return emptyListMessage;
    }

    /**
     * Get the widget displayed when the list has no rows.
     * 
     * @return the empty list widget
     */
    public Widget getEmptyListWidget() {
        return emptyListWidgetContainer.getWidget();
    }

    /**
     * Get the widget displayed when the data is loading.
     * 
     * @return the loading indicator
     */
    public Widget getLoadingIndicator() {
        return loadingIndicatorContainer.getWidget();
    }

    /**
     * Get the {@link Element} for the specified index. If the element has not
     * been created, null is returned.
     * 
     * @param indexOnPage the index on the page
     * @return the element, or null if it doesn't exists
     * @throws IndexOutOfBoundsException if the index is outside of the current
     *           page
     */
    public Element getRowElement(int indexOnPage) {
        getPresenter().flush();
        checkRowBounds(indexOnPage);
        if (childContainer.getChildCount() > indexOnPage) {
            return childContainer.getChild(indexOnPage).cast();
        }
        return null;
    }

    /**
     * Set the message to display when there is no data.
     * 
     * @param html the message to display when there are no results
     * @see #getEmptyListMessage()
     * @deprecated as of GWT 2.3, use
     *             {@link #setEmptyListWidget(com.google.gwt.user.client.ui.Widget)}
     *             instead
     */
    @Deprecated
    public void setEmptyListMessage(SafeHtml html) {
        this.emptyListMessage = html;
        setEmptyListWidget(html == null ? null : new HTML(html));
    }

    /**
     * Set the widget to display when the list has no rows.
     * 
     * @param widget the empty data widget
     */
    public void setEmptyListWidget(Widget widget) {
        emptyListWidgetContainer.setWidget(widget);
    }

    /**
     * Set the widget to display when the data is loading.
     * 
     * @param widget the loading indicator
     */
    public void setLoadingIndicator(Widget widget) {
        loadingIndicatorContainer.setWidget(widget);
    }

    /**
     * Set the value updater to use when cells modify items.
     * 
     * @param valueUpdater the {@link ValueUpdater}
     */
    public void setValueUpdater(ValueUpdater<T> valueUpdater) {
        this.valueUpdater = valueUpdater;
    }

    @Override
    protected boolean dependsOnSelection() {
        return cell.dependsOnSelection();
    }

    @Override
    protected void doAttachChildren() {
        try {
            doAttach(messagesPanel);
        } catch (Throwable e) {
            throw new AttachDetachException(Collections.singleton(e));
        }
    }

    @Override
    protected void doDetachChildren() {
        try {
            doDetach(messagesPanel);
        } catch (Throwable e) {
            throw new AttachDetachException(Collections.singleton(e));
        }
    }

    /**
     * Fire an event to the cell.
     * 
     * @param context the {@link Context} of the cell
     * @param event the event that was fired
     * @param parent the parent of the cell
     * @param value the value of the cell
     */
    protected void fireEventToCell(Context context, Event event, Element parent, T value) {
        Set<String> consumedEvents = cell.getConsumedEvents();
        if (consumedEvents != null && consumedEvents.contains(event.getType())) {
            boolean cellWasEditing = cell.isEditing(context, parent, value);
            cell.onBrowserEvent(context, parent, value, event, valueUpdater);
            cellIsEditing = cell.isEditing(context, parent, value);
            if (cellWasEditing && !cellIsEditing) {
                CellBasedWidgetImpl.get().resetFocus(new Scheduler.ScheduledCommand() {
                    @Override
                    public void execute() {
                        setFocus(true);
                    }
                });
            }
        }
    }

    /**
     * Return the cell used to render each item.
     */
    protected Cell<T> getCell() {
        return cell;
    }

    /**
     * Get the parent element that wraps the cell from the list item. Override
     * this method if you add structure to the element.
     * 
     * @param item the row element that wraps the list item
     * @return the parent element of the cell
     */
    protected Element getCellParent(Element item) {
        return item;
    }

    @Override
    protected Element getChildContainer() {
        return childContainer;
    }

    @Override
    protected Element getKeyboardSelectedElement() {
        // Do not use getRowElement() because that will flush the presenter.
        int rowIndex = getKeyboardSelectedRow();
        if (rowIndex >= 0 && childContainer.getChildCount() > rowIndex) {
            return childContainer.getChild(rowIndex).cast();
        }
        return null;
    }

    @Override
    protected boolean isKeyboardNavigationSuppressed() {
        return cellIsEditing;
    }

    @Override
    protected void onBlur() {
        // Remove the keyboard selection style.
        Element elem = getKeyboardSelectedElement();
        if (elem != null) {
            elem.removeClassName(style.cellListKeyboardSelectedItem());
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void onBrowserEvent2(Event event) {
        // Get the event target.
        EventTarget eventTarget = event.getEventTarget();
        if (!Element.is(eventTarget)) {
            return;
        }
        final Element target = event.getEventTarget().cast();

        // Forward the event to the cell.
        String idxString = "";
        Element cellTarget = target;
        while ((cellTarget != null) && ((idxString = cellTarget.getAttribute("__idx")).length() == 0)) {
            cellTarget = cellTarget.getParentElement();
        }
        if (idxString.length() > 0) {
            // Select the item if the cell does not consume events. Selection occurs
            // before firing the event to the cell in case the cell operates on the
            // currently selected item.
            String eventType = event.getType();
            boolean isClick = "click".equals(eventType);
            int idx = Integer.parseInt(idxString);
            int indexOnPage = idx - getPageStart();
            if (!isRowWithinBounds(indexOnPage)) {
                // If the event causes us to page, then the index will be out of bounds.
                return;
            }

            // Get the cell parent before doing selection in case the list is redrawn.
            boolean isSelectionHandled = cell.handlesSelection()
                    || KeyboardSelectionPolicy.BOUND_TO_SELECTION == getKeyboardSelectionPolicy();
            Element cellParent = getCellParent(cellTarget);
            T value = getVisibleItem(indexOnPage);
            Context context = new Context(idx, 0, getValueKey(value));
            CellPreviewEvent<T> previewEvent = CellPreviewEvent.fire(this, event, this, context, value,
                    cellIsEditing, isSelectionHandled);

            // Fire the event to the cell if the list has not been refreshed.
            if (!previewEvent.isCanceled()) {
                fireEventToCell(context, event, cellParent, value);
            }
        }
    }

    @Override
    protected void onFocus() {
        // Add the keyboard selection style.
        Element elem = getKeyboardSelectedElement();
        if (elem != null) {
            elem.addClassName(style.cellListKeyboardSelectedItem());
        }
    }

    /**
     * Called when the loading state changes.
     * 
     * @param state the new loading state
     */
    @Override
    protected void onLoadingStateChanged(LoadingState state) {
        Widget message = null;
        if (state == LoadingState.LOADING) {
            // Loading indicator.
            message = loadingIndicatorContainer;
        } else if (state == LoadingState.LOADED && getPresenter().isEmpty()) {
            // Empty table.
            message = emptyListWidgetContainer;
        }

        // Switch out the message to display.
        if (message != null) {
            messagesPanel.showWidget(messagesPanel.getWidgetIndex(message));
        }

        // Show the correct container.
        showOrHide(getChildContainer(), message == null);
        messagesPanel.setVisible(message != null);

        // Fire an event.
        super.onLoadingStateChanged(state);
    }

    @Override
    protected void renderRowValues(SafeHtmlBuilder sb, List<T> values, int start,
            SelectionModel<? super T> selectionModel) {
        String keyboardSelectedItem = " " + style.cellListKeyboardSelectedItem();
        String selectedItem = " " + style.cellListSelectedItem();
        String evenItem = style.cellListEvenItem();
        String oddItem = style.cellListOddItem();
        int keyboardSelectedRow = getKeyboardSelectedRow() + getPageStart();
        int length = values.size();
        int end = start + length;
        for (int i = start; i < end; i++) {
            T value = values.get(i - start);
            boolean isSelected = selectionModel == null ? false : selectionModel.isSelected(value);

            StringBuilder classesBuilder = new StringBuilder();
            classesBuilder.append(i % 2 == 0 ? evenItem : oddItem);
            if (isSelected) {
                classesBuilder.append(selectedItem);
            }

            SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder();
            Context context = new Context(i, 0, getValueKey(value));
            cell.render(context, value, cellBuilder);
            sb.append(TEMPLATE.div(i, classesBuilder.toString(), cellBuilder.toSafeHtml()));
        }
    }

    @Override
    protected boolean resetFocusOnCell() {
        int row = getKeyboardSelectedRow();
        if (isRowWithinBounds(row)) {
            Element rowElem = getKeyboardSelectedElement();
            Element cellParent = getCellParent(rowElem);
            T value = getVisibleItem(row);
            Context context = new Context(row + getPageStart(), 0, getValueKey(value));
            return cell.resetFocus(context, cellParent, value);
        }
        return false;
    }

    @Override
    protected void setKeyboardSelected(int index, boolean selected, boolean stealFocus) {
        if (!isRowWithinBounds(index)) {
            return;
        }

        Element elem = getRowElement(index);
        if (!selected || isFocused || stealFocus) {
            setStyleName(elem, style.cellListKeyboardSelectedItem(), selected);
        }
        setFocusable(elem, selected);
        if (selected && stealFocus && !cellIsEditing) {
            elem.focus();
            onFocus();
        }
    }

    /**
     * @deprecated this method is never called by AbstractHasData, render the
     *             selected styles in
     *             {@link #renderRowValues(SafeHtmlBuilder, List, int, SelectionModel)}
     */
    @Override
    @Deprecated
    protected void setSelected(Element elem, boolean selected) {
        setStyleName(elem, style.cellListSelectedItem(), selected);
    }
}