All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.user.cellview.client.CellBrowser Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.animation.client.Animation;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.OpenEvent;
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.i18n.client.LocaleInfo;
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.safecss.shared.SafeStyles;
import com.google.gwt.safecss.shared.SafeStylesBuilder;
import com.google.gwt.safecss.shared.SafeStylesUtils;
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.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasAnimation;
import com.google.gwt.user.client.ui.ProvidesResize;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.HasRows;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionModel;
import com.google.gwt.view.client.TreeViewModel;
import com.google.gwt.view.client.TreeViewModel.NodeInfo;

import java.util.ArrayList;
import java.util.List;

/**
 * A "browsable" view of a tree in which only a single node per level may be
 * open at one time.
 * 
 * 

* This widget will only work in standards mode, which requires that * the HTML page in which it is run have an explicit <!DOCTYPE> * declaration. *

* *

*

Example

*
*
Trivial example
*
{@example com.google.gwt.examples.cellview.CellBrowserExample}
*
Complex example
*
{@example com.google.gwt.examples.cellview.CellBrowserExample2}
*
*/ public class CellBrowser extends AbstractCellTree implements ProvidesResize, RequiresResize, HasAnimation { /** * A ClientBundle that provides images for this widget. */ public interface Resources extends ClientBundle { /** * An image indicating a closed branch. */ @ImageOptions(flipRtl = true) ImageResource cellBrowserClosed(); /** * An image indicating an open branch. */ @ImageOptions(flipRtl = true) ImageResource cellBrowserOpen(); /** * The background used for open items. */ // Use RepeatStyle.BOTH to ensure that we do not bundle the image. @ImageOptions(repeatStyle = RepeatStyle.Both, flipRtl = true) ImageResource cellBrowserOpenBackground(); /** * The background used for selected items. */ // Use RepeatStyle.BOTH to ensure that we do not bundle the image. @Source("cellTreeSelectedBackground.png") @ImageOptions(repeatStyle = RepeatStyle.Both, flipRtl = true) ImageResource cellBrowserSelectedBackground(); /** * The styles used in this widget. */ @Source(Style.DEFAULT_CSS) Style cellBrowserStyle(); } /** * Styles used by this widget. */ @ImportedWithPrefix("gwt-CellBrowser") 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/CellBrowser.css"; /** * Applied to all columns. */ String cellBrowserColumn(); /** * Applied to even list items. */ String cellBrowserEvenItem(); /** * Applied to the first column. */ String cellBrowserFirstColumn(); /*** * Applied to keyboard selected items. */ String cellBrowserKeyboardSelectedItem(); /** * Applied to odd list items. */ String cellBrowserOddItem(); /*** * Applied to open items. */ String cellBrowserOpenItem(); /*** * Applied to selected items. */ String cellBrowserSelectedItem(); /** * Applied to the widget. */ String cellBrowserWidget(); } interface Template extends SafeHtmlTemplates { @Template("
{3}
{4}
") SafeHtml div(int idx, String classes, SafeStyles padding, SafeHtml imageHtml, SafeHtml cellContents); @Template("
{4}
{5}
") SafeHtml divFocusable(int idx, String classes, SafeStyles padding, int tabIndex, SafeHtml imageHtml, SafeHtml cellContents); @Template("
{5}
{6}
") SafeHtml divFocusableWithKey(int idx, String classes, SafeStyles padding, int tabIndex, char accessKey, SafeHtml imageHtml, SafeHtml cellContents); @Template("
{1}
") SafeHtml imageWrapper(SafeStyles css, SafeHtml image); } /** * A custom version of cell list used by the browser. Visible for testing. * * @param the data type of list items */ class BrowserCellList extends CellList { /** * The level of this list view. */ private final int level; /** * The key of the currently focused item. */ private Object focusedKey; /** * The currently selected value in this list. */ private T selectedValue; /** * A boolean indicating that this widget is no longer used. */ private boolean isDestroyed; /** * Indicates whether or not the focused value is open. */ private boolean isFocusedOpen; /** * Temporary element used to create elements from HTML. */ private final Element tmpElem = Document.get().createDivElement(); public BrowserCellList(final Cell cell, int level, ProvidesKey keyProvider) { super(cell, cellListResources, keyProvider); this.level = level; } protected void deselectValue() { SelectionModel selectionModel = getSelectionModel(); if (selectionModel != null && selectedValue != null) { selectionModel.setSelected(selectedValue, false); } } @Override protected Element getCellParent(Element item) { return item.getFirstChildElement().getNextSiblingElement(); } @Override protected boolean isKeyboardNavigationSuppressed() { /* * Keyboard selection is never disabled in this list because we use it to * track the open node, but we want to suppress keyboard navigation if the * user disables it. */ return KeyboardSelectionPolicy.DISABLED == CellBrowser.this.getKeyboardSelectionPolicy() || super.isKeyboardNavigationSuppressed(); } @Override protected void onBrowserEvent2(Event event) { super.onBrowserEvent2(event); // Handle keyboard navigation between lists. String eventType = event.getType(); if (BrowserEvents.KEYDOWN.equals(eventType) && !isKeyboardNavigationSuppressed()) { int keyCode = event.getKeyCode(); boolean isRtl = LocaleInfo.getCurrentLocale().isRTL(); keyCode = KeyCodes.maybeSwapArrowKeysForRtl(keyCode, isRtl); switch (keyCode) { case KeyCodes.KEY_LEFT: keyboardNavigateShallow(); return; case KeyCodes.KEY_RIGHT: keyboardNavigateDeep(); return; } } } @Override protected void renderRowValues(SafeHtmlBuilder sb, List values, int start, SelectionModel selectionModel) { Cell cell = getCell(); String keyboardSelectedItem = " " + style.cellBrowserKeyboardSelectedItem(); String selectedItem = " " + style.cellBrowserSelectedItem(); String openItem = " " + style.cellBrowserOpenItem(); String evenItem = style.cellBrowserEvenItem(); String oddItem = style.cellBrowserOddItem(); 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); boolean isOpen = isOpen(i); StringBuilder classesBuilder = new StringBuilder(); classesBuilder.append(i % 2 == 0 ? evenItem : oddItem); if (isOpen) { classesBuilder.append(openItem); } if (isSelected) { classesBuilder.append(selectedItem); } SafeHtmlBuilder cellBuilder = new SafeHtmlBuilder(); Context context = new Context(i, 0, getValueKey(value)); cell.render(context, value, cellBuilder); // Figure out which image to use. SafeHtml image; if (isOpen) { image = openImageHtml; } else if (isLeaf(value)) { image = LEAF_IMAGE; } else { image = closedImageHtml; } SafeStyles padding = SafeStylesUtils.fromTrustedString("padding-right: " + imageWidth + "px;"); if (i == keyboardSelectedRow) { // This is the focused item. if (isFocused) { classesBuilder.append(keyboardSelectedItem); } char accessKey = getAccessKey(); if (accessKey != 0) { sb.append(template.divFocusableWithKey(i, classesBuilder.toString(), padding, getTabIndex(), getAccessKey(), image, cellBuilder.toSafeHtml())); } else { sb.append(template.divFocusable(i, classesBuilder.toString(), padding, getTabIndex(), image, cellBuilder.toSafeHtml())); } } else { sb.append(template.div(i, classesBuilder.toString(), padding, image, cellBuilder .toSafeHtml())); } } // Update the child state. updateChildState(this, true); } @Override protected void setKeyboardSelected(int index, boolean selected, boolean stealFocus) { super.setKeyboardSelected(index, selected, stealFocus); if (!isRowWithinBounds(index)) { return; } // Update the style. Element elem = getRowElement(index); T value = getPresenter().getVisibleItem(index); boolean isOpen = selected && isOpen(index); setStyleName(elem, style.cellBrowserOpenItem(), isOpen); // Update the image. SafeHtml image = null; if (isOpen) { image = openImageHtml; } else if (getTreeViewModel().isLeaf(value)) { image = LEAF_IMAGE; } else { image = closedImageHtml; } tmpElem.setInnerSafeHtml(image); elem.replaceChild(tmpElem.getFirstChildElement(), elem.getFirstChildElement()); // Update the open state. updateChildState(this, true); } /** * Set the selected value in this list. If there is already a selected * value, the old value will be deselected. * * @param value the selected value */ protected void setSelectedValue(T value) { // Early exit if the value is unchanged. Object oldKey = getValueKey(selectedValue); Object newKey = getValueKey(value); if (newKey != null && newKey.equals(oldKey)) { return; } // Deselect the current value. Only one thing is selected at a time. deselectValue(); // Select the new value. SelectionModel selectionModel = getSelectionModel(); if (selectionModel != null) { selectedValue = value; selectionModel.setSelected(selectedValue, true); } } /** * Check if the specified index is currently open. An index is open if it is * the keyboard selected index, there is an associated keyboard selected * value, and the value is not a leaf. * * @param index the index * @return true if open, false if not */ private boolean isOpen(int index) { T value = getPresenter().getKeyboardSelectedRowValue(); return index == getKeyboardSelectedRow() && value != null && !getTreeViewModel().isLeaf(value); } /** * Navigate to a deeper node. */ private void keyboardNavigateDeep() { if (isKeyboardSelectionDisabled()) { return; } // Move to the child node. if (level < treeNodes.size() - 1) { TreeNodeImpl treeNode = treeNodes.get(level + 1); treeNode.display.getPresenter().setKeyboardSelectedRow( treeNode.display.getKeyboardSelectedRow(), true, true); } } /** * Navigate to a shallower node. */ private void keyboardNavigateShallow() { if (isKeyboardSelectionDisabled()) { return; } // Move to the parent node. if (level > 0) { TreeNodeImpl treeNode = treeNodes.get(level - 1); treeNode.display.setFocus(true); } } } /** * A node in the tree. * * @param the data type of the children of the node */ class TreeNodeImpl implements TreeNode { private final BrowserCellList display; private NodeInfo nodeInfo; private final Object value; private final HandlerRegistration valueChangeHandler; private final Widget widget; /** * Construct a new {@link TreeNodeImpl}. * * @param nodeInfo the nodeInfo for the children nodes * @param value the value of the node * @param display the display associated with the node * @param widget the widget that wraps the display */ public TreeNodeImpl(final NodeInfo nodeInfo, Object value, final BrowserCellList display, Widget widget) { this.display = display; this.nodeInfo = nodeInfo; this.value = value; this.widget = widget; // Trim to the current level if the open node disappears. valueChangeHandler = display.addValueChangeHandler(new ValueChangeHandler>() { @Override public void onValueChange(ValueChangeEvent> event) { Object focusedKey = display.focusedKey; if (focusedKey != null) { boolean stillExists = false; List displayValues = event.getValue(); for (C displayValue : displayValues) { if (focusedKey.equals(display.getValueKey(displayValue))) { stillExists = true; break; } } if (!stillExists) { trimToLevel(display.level); } } } }); } @Override public int getChildCount() { assertNotDestroyed(); return display.getPresenter().getVisibleItemCount(); } @Override public C getChildValue(int index) { assertNotDestroyed(); checkChildBounds(index); return display.getVisibleItem(index); } @Override public int getIndex() { assertNotDestroyed(); TreeNodeImpl parent = getParent(); return (parent == null) ? 0 : parent.getOpenIndex(); } @Override public TreeNodeImpl getParent() { assertNotDestroyed(); return getParentImpl(); } @Override public Object getValue() { return value; } @Override public boolean isChildLeaf(int index) { assertNotDestroyed(); checkChildBounds(index); return isLeaf(getChildValue(index)); } @Override public boolean isChildOpen(int index) { assertNotDestroyed(); checkChildBounds(index); return (display.focusedKey == null || !display.isFocusedOpen) ? false : display.focusedKey .equals(display.getValueKey(getChildValue(index))); } @Override public boolean isDestroyed() { if (nodeInfo != null) { /* * Flush the parent display because the user may have replaced this * node, which would destroy it. */ TreeNodeImpl parent = getParentImpl(); if (parent != null && !parent.isDestroyed()) { parent.display.getPresenter().flush(); } } return nodeInfo == null; } @Override public TreeNode setChildOpen(int index, boolean open) { return setChildOpen(index, open, true); } @Override public TreeNode setChildOpen(int index, boolean open, boolean fireEvents) { assertNotDestroyed(); checkChildBounds(index); if (open) { // Open the child node. display.getPresenter().setKeyboardSelectedRow(index, false, true); return updateChildState(display, fireEvents); } else { // Close the child node if it is currently open. if (index == display.getKeyboardSelectedRow()) { display.getPresenter().clearKeyboardSelectedRowValue(); updateChildState(display, fireEvents); } return null; } } BrowserCellList getDisplay() { return display; } /** * Return the key of the value that is focused in this node's display. */ Object getFocusedKey() { return display.focusedKey; } /** * Return true if the focused value is open, false if not. */ boolean isFocusedOpen() { return display.isFocusedOpen; } /** * Assert that the node has not been destroyed. */ private void assertNotDestroyed() { if (isDestroyed()) { throw new IllegalStateException("TreeNode no longer exists."); } } /** * Check the child bounds. * * @param index the index of the child * @throws IndexOutOfBoundsException if the child is not in range */ private void checkChildBounds(int index) { if ((index < 0) || (index >= getChildCount())) { throw new IndexOutOfBoundsException(); } } /** * Unregister the list view and remove it from the widget. */ private void destroy() { display.isDestroyed = true; valueChangeHandler.removeHandler(); display.deselectValue(); display.setSelectionModel(null); nodeInfo.unsetDataDisplay(); getSplitLayoutPanel().remove(widget); nodeInfo = null; } /** * Get the index of the open item. * * @return the index of the open item, or -1 if not found */ private int getOpenIndex() { return display.isFocusedOpen ? display.getKeyboardSelectedRow() : -1; } /** * Get the parent node without checking if this node is destroyed. * * @return the parent node, or null if the node has no parent */ private TreeNodeImpl getParentImpl() { return (display.level == 0) ? null : treeNodes.get(display.level - 1); } } /** * An implementation of {@link CellList.Resources} that delegates to * {@link CellBrowser.Resources}. */ private static class CellListResourcesImpl implements CellList.Resources { private final CellBrowser.Resources delegate; private final CellListStyleImpl style; public CellListResourcesImpl(CellBrowser.Resources delegate) { this.delegate = delegate; this.style = new CellListStyleImpl(delegate.cellBrowserStyle()); } @Override public ImageResource cellListSelectedBackground() { return delegate.cellBrowserSelectedBackground(); } @Override public CellList.Style cellListStyle() { return style; } } /** * An implementation of {@link CellList.Style} that delegates to * {@link CellBrowser.Style}. */ private static class CellListStyleImpl implements CellList.Style { private final CellBrowser.Style delegate; public CellListStyleImpl(CellBrowser.Style delegate) { this.delegate = delegate; } @Override public String cellListEvenItem() { return delegate.cellBrowserEvenItem(); } @Override public String cellListKeyboardSelectedItem() { return delegate.cellBrowserKeyboardSelectedItem(); } @Override public String cellListOddItem() { return delegate.cellBrowserOddItem(); } @Override public String cellListSelectedItem() { return delegate.cellBrowserSelectedItem(); } @Override public String cellListWidget() { // Do not apply any style to the list itself. return null; } @Override public boolean ensureInjected() { return delegate.ensureInjected(); } @Override public String getName() { return delegate.getName(); } @Override public String getText() { return delegate.getText(); } } /** * The animation used to scroll to the newly added list view. */ private class ScrollAnimation extends Animation { /** * The starting scroll position. */ private int startScrollLeft; /** * The ending scroll position. */ private int targetScrollLeft; @Override protected void onComplete() { getElement().setScrollLeft(targetScrollLeft); } @Override protected void onUpdate(double progress) { int diff = targetScrollLeft - startScrollLeft; getElement().setScrollLeft(startScrollLeft + (int) (diff * progress)); } void scrollToEnd() { Element elem = getElement(); targetScrollLeft = elem.getScrollWidth() - elem.getClientWidth(); if (LocaleInfo.getCurrentLocale().isRTL()) { targetScrollLeft *= -1; } if (isAnimationEnabled()) { // Animate the scrolling. startScrollLeft = elem.getScrollLeft(); run(250, elem); } else { // Scroll instantly. onComplete(); } } } /** * Pager factory used to create pagers for each {@link CellList} of the * {@link CellBrowser}. */ public static interface PagerFactory { AbstractPager create(HasRows display); } /** * Default pager. */ private static class PageSizePagerFactory implements PagerFactory { @Override public AbstractPager create(HasRows display) { return new PageSizePager(display.getVisibleRange().getLength()); } } /** * Builder object to create CellBrowser. * * @param the type of data in the root node */ public static class Builder { private final TreeViewModel viewModel; private final T rootValue; private Widget loadingIndicator; private PagerFactory pagerFactory = new PageSizePagerFactory(); private Integer pageSize; private Resources resources; /** * Construct a new {@link Builder}. * * @param viewModel the {@link TreeViewModel} that backs the tree * @param rootValue the hidden root value of the tree */ public Builder(TreeViewModel viewModel, T rootValue) { this.viewModel = viewModel; this.rootValue = rootValue; } /** * Creates a new {@link CellBrowser}. * * @return new {@link CellBrowser} */ public CellBrowser build() { return new CellBrowser(this); } /** * Set the widget to display when the data is loading. * * @param widget the loading indicator * @return this */ public Builder loadingIndicator(Widget widget) { this.loadingIndicator = widget; return this; } /** * Set the pager factory used to create pagers for each {@link CellList}. * Defaults to {@link PageSizePagerFactory} if not set. * * Can be set to null if no pager should be used. You should also set pageSize * big enough to hold all your data then. * * @param factory the pager factory * @return this */ public Builder pagerFactory(PagerFactory factory) { this.pagerFactory = factory; return this; } /** * Set the pager size for each {@link CellList}. * * @param pageSize the page size * @return this */ public Builder pageSize(int pageSize) { this.pageSize = pageSize; return this; } /** * Set resources used for images. * * @param resources the {@link Resources} used for images * @return this */ public Builder resources(Resources resources) { this.resources = resources; return this; } private Resources resources() { if (resources == null) { resources = getDefaultResources(); } return resources; } } private static Resources DEFAULT_RESOURCES; /** * The element used in place of an image when a node has no children. */ private static final SafeHtml LEAF_IMAGE = SafeHtmlUtils .fromSafeConstant("
"); private static Template template; private static Resources getDefaultResources() { if (DEFAULT_RESOURCES == null) { DEFAULT_RESOURCES = GWT.create(Resources.class); } return DEFAULT_RESOURCES; } /** * The visible {@link TreeNodeImpl}s. Visible for testing. */ final List> treeNodes = new ArrayList>(); /** * The animation used for scrolling. */ private final ScrollAnimation animation = new ScrollAnimation(); /** * The resources used by the {@link CellList}. */ private final CellList.Resources cellListResources; /** * The HTML used to generate the closed image. */ private final SafeHtml closedImageHtml; /** * The default width of new columns. */ private int defaultWidth = 200; /** * The maximum width of the open and closed images. */ private final int imageWidth; /** * A boolean indicating whether or not animations are enabled. */ private boolean isAnimationEnabled; /** * Widget passed to CellLists. */ private final Widget loadingIndicator; /** * The minimum width of new columns. */ private int minWidth; /** * The HTML used to generate the open image. */ private final SafeHtml openImageHtml; /** * Factory used to create pagers for CellLists. */ private final PagerFactory pagerFactory; /** * Page size for CellLists. */ private final Integer pageSize; /** * The element used to maintain the scrollbar when columns are removed. */ private Element scrollLock; /** * The styles used by this widget. */ private final Style style; /** * Construct a new {@link CellBrowser}. * * @param the type of data in the root node * @param viewModel the {@link TreeViewModel} that backs the tree * @param rootValue the hidden root value of the tree * * @deprecated please use {@link Builder} */ public CellBrowser(TreeViewModel viewModel, T rootValue) { this(new Builder(viewModel, rootValue)); } /** * Construct a new {@link CellBrowser} with the specified {@link Resources}. * * @param the type of data in the root node * @param viewModel the {@link TreeViewModel} that backs the tree * @param rootValue the hidden root value of the tree * @param resources the {@link Resources} used for images * * @deprecated please use {@link Builder} */ public CellBrowser(TreeViewModel viewModel, T rootValue, Resources resources) { this(new Builder(viewModel, rootValue).resources(resources)); } protected CellBrowser(Builder builder) { super(builder.viewModel); if (template == null) { template = GWT.create(Template.class); } Resources resources = builder.resources(); this.style = resources.cellBrowserStyle(); this.style.ensureInjected(); this.cellListResources = new CellListResourcesImpl(resources); this.loadingIndicator = builder.loadingIndicator; this.pagerFactory = builder.pagerFactory; this.pageSize = builder.pageSize; initWidget(new SplitLayoutPanel()); getElement().getStyle().setOverflow(Overflow.AUTO); setStyleName(this.style.cellBrowserWidget()); // Initialize the open and close images strings. ImageResource treeOpen = resources.cellBrowserOpen(); ImageResource treeClosed = resources.cellBrowserClosed(); openImageHtml = getImageHtml(treeOpen); closedImageHtml = getImageHtml(treeClosed); imageWidth = Math.max(treeOpen.getWidth(), treeClosed.getWidth()); minWidth = imageWidth + 20; // Add a placeholder to maintain the scroll width. scrollLock = Document.get().createDivElement(); scrollLock.getStyle().setPosition(Position.ABSOLUTE); scrollLock.getStyle().setVisibility(Visibility.HIDDEN); scrollLock.getStyle().setZIndex(-32767); scrollLock.getStyle().setTop(0, Unit.PX); if (LocaleInfo.getCurrentLocale().isRTL()) { scrollLock.getStyle().setRight(0, Unit.PX); } else { scrollLock.getStyle().setLeft(0, Unit.PX); } scrollLock.getStyle().setHeight(1, Unit.PX); scrollLock.getStyle().setWidth(1, Unit.PX); getElement().appendChild(scrollLock); // Associate the first view with the rootValue. appendTreeNode(getNodeInfo(builder.rootValue), builder.rootValue); // Catch scroll events. sinkEvents(Event.ONSCROLL); } /** * Get the default width of new columns. * * @return the default width in pixels * @see #setDefaultColumnWidth(int) */ public int getDefaultColumnWidth() { return defaultWidth; } /** * Get the minimum width of columns. * * @return the minimum width in pixels * @see #setMinimumColumnWidth(int) */ public int getMinimumColumnWidth() { return minWidth; } @Override public TreeNode getRootTreeNode() { return treeNodes.get(0); } @Override public boolean isAnimationEnabled() { return isAnimationEnabled; } @Override public void onBrowserEvent(Event event) { switch (DOM.eventGetType(event)) { case Event.ONSCROLL: // Shorten the scroll bar is possible. adjustScrollLock(); break; } super.onBrowserEvent(event); } @Override public void onResize() { getSplitLayoutPanel().onResize(); } @Override public void setAnimationEnabled(boolean enable) { this.isAnimationEnabled = enable; } /** * Set the default width of new columns. * * @param width the default width in pixels * @see #getDefaultColumnWidth() */ public void setDefaultColumnWidth(int width) { this.defaultWidth = width; } /** * Set the minimum width of columns. * * @param minWidth the minimum width in pixels * @see #getMinimumColumnWidth() */ public void setMinimumColumnWidth(int minWidth) { this.minWidth = minWidth; } /** * Create a pager to control the list view. * * @param the item type in the list view * @param display the list view to add paging too * @return the pager */ protected Widget createPager(HasData display) { if (pagerFactory == null) { return null; } AbstractPager pager = pagerFactory.create(display); pager.setDisplay(display); return pager; } /** * Adjust the size of the scroll lock element based on the new position of the * scroll bar. */ private void adjustScrollLock() { int scrollLeft = Math.abs(getElement().getScrollLeft()); if (scrollLeft > 0) { int clientWidth = getElement().getClientWidth(); scrollLock.getStyle().setWidth(scrollLeft + clientWidth, Unit.PX); } else { scrollLock.getStyle().setWidth(1.0, Unit.PX); } } /** * Create a new {@link TreeNodeImpl} and append it to the end of the * LayoutPanel. * * @param the data type of the children * @param nodeInfo the info about the node * @param value the value of the open node */ private TreeNode appendTreeNode(final NodeInfo nodeInfo, Object value) { // Create the list view. final int level = treeNodes.size(); final BrowserCellList view = createDisplay(nodeInfo, level); // Create a pager and wrap the components in a scrollable container. Set the // tabIndex to -1 so the user can tab between lists without going through // the scrollable. ScrollPanel scrollable = new ScrollPanel(); scrollable.getElement().setTabIndex(-1); final Widget pager = createPager(view); if (pager != null) { FlowPanel flowPanel = new FlowPanel(); flowPanel.add(view); flowPanel.add(pager); scrollable.setWidget(flowPanel); } else { scrollable.setWidget(view); } scrollable.setStyleName(style.cellBrowserColumn()); if (level == 0) { scrollable.addStyleName(style.cellBrowserFirstColumn()); } // Create a TreeNode. TreeNodeImpl treeNode = new TreeNodeImpl(nodeInfo, value, view, scrollable); treeNodes.add(treeNode); /* * Attach the view to the selection model and node info. Nullify the default * selection manager because it is provided by the node info. */ view.setSelectionModel(nodeInfo.getSelectionModel(), null); nodeInfo.setDataDisplay(view); // Add the view to the LayoutPanel. SplitLayoutPanel splitPanel = getSplitLayoutPanel(); splitPanel.insertLineStart(scrollable, defaultWidth, null); splitPanel.setWidgetMinSize(scrollable, minWidth); splitPanel.forceLayout(); // Scroll to the right. animation.scrollToEnd(); return treeNode; } /** * Create a {@link HasData} that will display items. The {@link HasData} must * extend {@link Widget}. * * @param the item type in the list view * @param nodeInfo the node info with child data * @param level the level of the list * @return the {@link HasData} */ private BrowserCellList createDisplay(NodeInfo nodeInfo, int level) { BrowserCellList display = new BrowserCellList(nodeInfo.getCell(), level, nodeInfo.getProvidesKey()); if (loadingIndicator != null) { display.setLoadingIndicator(loadingIndicator); } if (pageSize != null) { display.setPageSize(pageSize); } display.setValueUpdater(nodeInfo.getValueUpdater()); /* * A CellBrowser has a single keyboard selection policy and multiple lists, * so we're not using the selection policy in each list. Leave them on all * the time because we use keyboard selection to keep track of which item is * open (selected) at each level. */ display.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.ENABLED); return display; } /** * Get the HTML representation of an image. * * @param res the {@link ImageResource} to render as HTML * @return the rendered HTML */ private SafeHtml getImageHtml(ImageResource res) { // Right-justify image if LTR, left-justify if RTL AbstractImagePrototype proto = AbstractImagePrototype.create(res); SafeHtml image = SafeHtmlUtils.fromTrustedString(proto.getHTML()); SafeStylesBuilder cssBuilder = new SafeStylesBuilder(); if (LocaleInfo.getCurrentLocale().isRTL()) { cssBuilder.appendTrustedString("left:0px;"); } else { cssBuilder.appendTrustedString("right:0px;"); } cssBuilder.appendTrustedString("width: " + res.getWidth() + "px;"); cssBuilder.appendTrustedString("height: " + res.getHeight() + "px;"); return template.imageWrapper(cssBuilder.toSafeStyles(), image); } /** * Get the {@link SplitLayoutPanel} used to lay out the views. * * @return the {@link SplitLayoutPanel} */ private SplitLayoutPanel getSplitLayoutPanel() { return (SplitLayoutPanel) getWidget(); } /** * Reduce the number of {@link HasData}s down to the specified level. * * @param level the level to trim to */ private void trimToLevel(int level) { // Add a placeholder to maintain the same scroll width. adjustScrollLock(); // Remove the views that are no longer needed. int curLevel = treeNodes.size() - 1; while (curLevel > level) { TreeNodeImpl removed = treeNodes.remove(curLevel); removed.destroy(); curLevel--; } // Nullify the focused key at the level. if (level < treeNodes.size()) { TreeNodeImpl node = treeNodes.get(level); node.display.focusedKey = null; node.display.isFocusedOpen = false; } } /** * Update the state of a child node based on the keyboard selection of the * specified {@link BrowserCellList}. This method will open/close child * {@link TreeNode}s as needed. * * @param cellList the CellList that changed state. * @param fireEvents true to fireEvents * @return the open {@link TreeNode}, or null if not opened */ private TreeNode updateChildState(BrowserCellList cellList, boolean fireEvents) { /* * Verify that the specified list is still in the browser. It possible for * the list to receive deferred updates after it has been removed */ if (cellList.isDestroyed) { return null; } // Get the key of the value to open. C newValue = cellList.getPresenter().getKeyboardSelectedRowValue(); Object newKey = cellList.getValueKey(newValue); // Close the current open node. TreeNode closedNode = null; if (cellList.focusedKey != null && cellList.isFocusedOpen && !cellList.focusedKey.equals(newKey)) { // Get the node to close. closedNode = (treeNodes.size() > cellList.level + 1) ? treeNodes.get(cellList.level + 1) : null; // Close the node. trimToLevel(cellList.level); } // Open the new node. TreeNode openNode = null; boolean justOpenedNode = false; if (newKey != null) { if (newKey.equals(cellList.focusedKey)) { // The node is already open. openNode = cellList.isFocusedOpen ? treeNodes.get(cellList.level + 1) : null; } else { // Select this value. if (KeyboardSelectionPolicy.BOUND_TO_SELECTION == getKeyboardSelectionPolicy()) { cellList.setSelectedValue(newValue); } // Add the child node if this node has children. cellList.focusedKey = newKey; NodeInfo childNodeInfo = isLeaf(newValue) ? null : getNodeInfo(newValue); if (childNodeInfo != null) { cellList.isFocusedOpen = true; justOpenedNode = true; openNode = appendTreeNode(childNodeInfo, newValue); } } } /* * Fire event. We fire events after updating the view in case user event * handlers modify the open state of nodes, which would interrupt the * process. */ if (fireEvents) { if (closedNode != null) { CloseEvent.fire(this, closedNode); } if (openNode != null && justOpenedNode) { OpenEvent.fire(this, openNode); } } // Return the open node if it is still open. return (openNode == null || openNode.isDestroyed()) ? null : openNode; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy