Package com.sencha.gxt.widget.core.client.treegrid

Source Code of com.sencha.gxt.widget.core.client.treegrid.TreeGrid$TreeGridNode

/**
* Sencha GXT 3.1.0-beta - Sencha for GWT
* Copyright(c) 2007-2014, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.widget.core.client.treegrid;

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

import com.google.gwt.cell.client.Cell;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Event;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.dom.XDOM;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.shared.FastMap;
import com.sencha.gxt.data.shared.IconProvider;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.ModelKeyProvider;
import com.sencha.gxt.data.shared.PropertyAccess;
import com.sencha.gxt.data.shared.TreeStore;
import com.sencha.gxt.data.shared.event.StoreAddEvent;
import com.sencha.gxt.data.shared.event.StoreClearEvent;
import com.sencha.gxt.data.shared.event.StoreDataChangeEvent;
import com.sencha.gxt.data.shared.event.StoreFilterEvent;
import com.sencha.gxt.data.shared.event.StoreHandlers;
import com.sencha.gxt.data.shared.event.StoreRecordChangeEvent;
import com.sencha.gxt.data.shared.event.StoreRemoveEvent;
import com.sencha.gxt.data.shared.event.StoreSortEvent;
import com.sencha.gxt.data.shared.event.StoreUpdateEvent;
import com.sencha.gxt.data.shared.event.TreeStoreRemoveEvent;
import com.sencha.gxt.data.shared.loader.TreeLoader;
import com.sencha.gxt.messages.client.DefaultMessages;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent.BeforeCollapseItemHandler;
import com.sencha.gxt.widget.core.client.event.BeforeCollapseItemEvent.HasBeforeCollapseItemHandlers;
import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent;
import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent.BeforeExpandItemHandler;
import com.sencha.gxt.widget.core.client.event.BeforeExpandItemEvent.HasBeforeExpandItemHandlers;
import com.sencha.gxt.widget.core.client.event.CollapseItemEvent;
import com.sencha.gxt.widget.core.client.event.CollapseItemEvent.CollapseItemHandler;
import com.sencha.gxt.widget.core.client.event.CollapseItemEvent.HasCollapseItemHandlers;
import com.sencha.gxt.widget.core.client.event.ExpandItemEvent;
import com.sencha.gxt.widget.core.client.event.ExpandItemEvent.ExpandItemHandler;
import com.sencha.gxt.widget.core.client.event.ExpandItemEvent.HasExpandItemHandlers;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.GridView.GridAppearance;
import com.sencha.gxt.widget.core.client.tree.Tree.Joint;
import com.sencha.gxt.widget.core.client.tree.Tree.TreeAppearance;
import com.sencha.gxt.widget.core.client.tree.Tree.TreeNode;
import com.sencha.gxt.widget.core.client.tree.TreeStyle;

/**
* A {@link TreeGrid} provides support for displaying and editing hierarchical data where each item may contain multiple
* properties. The tree grid gets its data from a {@link TreeStore} and its column definitions from a
* {@link ColumnModel}. Each model in the store is rendered as an item in the tree. The fields in the model provide the
* data for each column associated with the item. Any updates to the store are automatically pushed to the tree grid.
* This includes inserting, removing, sorting and filter.
* <p/>
* In GXT version 3, {@link ModelKeyProvider}s and {@link ValueProvider}s provide the interface between your data model
* and the list store and {@link ColumnConfig} classes. This enables a tree grid to work with data of any object type.
* <p/>
* You can provide your own implementation of these interfaces, or you can use a Sencha supplied generator to create
* them for you automatically. A generator runs at compile time to create a Java class that is compiled to JavaScript.
* The Sencha supplied generator can create classes for interfaces that extend the {@link PropertyAccess} interface. The
* generator transparently creates the class at compile time and the {@link GWT#create(Class)} method returns an
* instance of that class at run time. The generated class is managed by GWT and GXT and you generally do not need to
* worry about what the class is called, where it is located, or other similar details.
* <p/>
* Each tree grid has a {@link GridView}. The grid view provides many options for customizing the grid's appearance
* (e.g. striping, mouse-over tracking, empty text). To set these options, get the current grid view using
* {@link TreeGrid#getView()} and then set the desired option on the grid view.
* <p/>
* To customize the appearance of a column in a tree grid, provide a cell implementation using
* {@link ColumnConfig#setCell(Cell)}.
* <p/>
* Tree grids support several ways to manage column widths:
* <ol>
* <li>The most basic approach is to simply give pixel widths to each column. Columns widths will match the specified
* values.</li>
* <li>A column can be identified as an auto-expand column. As the width of the tree grid changes, or columns are
* resized, the specified column's width is adjusted so that the column fills the available width with no horizontal
* scrolling. See @link {@link GridView#setAutoExpandColumn(ColumnConfig)}.</li>
* <li>The tree grid can resize columns based on relative weights, determined by the pixel width assigned to each
* column. As the width of the tree grid or columns change, the weight is used to allocate the available space. Use
* {@link GridView#setAutoFill(boolean)} or {@link GridView#setForceFit(boolean)} to enable this feature:</li>
* <ul>
* <li>With auto fill, the calculations are run when the tree grid is created (or reconfigured). After the tree grid is
* rendered, the column widths will not be adjusted when the available width changes.</li>
* <li>With force fit the width calculations are run every time there are changes to the available width or column
* sizes.</li>
* </ul>
* <li>To prevent a column from participating in auto fill or force fit, use {@link ColumnConfig#setFixed(boolean)}.</li>
* </ol>
* </p>
* The following code snippet illustrates the creation of a simple tree grid with local data for test purposes. For more
* practical examples that show how to load data from remote sources, see the Async TreeGrid example in the online
* Explorer demo.</p>
*
* <pre>{@code
    // Generate the key provider and value provider for the Data class
    DataProperties dp = GWT.create(DataProperties.class);

    // Create the configurations for each column in the tree grid
    List<ColumnConfig<Data, ?>> ccs = new LinkedList<ColumnConfig<Data, ?>>();
    ccs.add(new ColumnConfig<Data, String>(dp.name(), 200, "Name"));
    ccs.add(new ColumnConfig<Data, String>(dp.value(), 200, "Value"));
    ColumnModel<Data> cm = new ColumnModel<Test.Data>(ccs);

    // Create the store that the contains the data to display in the tree grid
    TreeStore<Data> s = new TreeStore<Test.Data>(dp.key());

    Data r1 = new Data("Parent 1", "value1");
    s.add(r1);
    s.add(r1, new Data("Child 1.1", "value2"));
    s.add(r1, new Data("Child 1.2", "value3"));

    Data r2 = new Data("Parent 2", "value4");
    s.add(r2);
    s.add(r2, new Data("Child 2.1", "value5"));
    s.add(r2, new Data("Child 2.2", "value6"));

    // Create the tree grid using the store, column model and column config for the tree column
    TreeGrid<Data> tg = new TreeGrid<Data>(s, cm, ccs.get(0));

    // Add the tree to a container
    RootPanel.get().add(tg);
* }</pre>
* <p/>
* To use the Sencha supplied generator to create model key providers and value providers, extend the
* <code>PropertyAccess</code> interface, parameterized with the type of data you want to access (as shown below) and
* invoke the <code>GWT.create</code> method on its <code>class</code> member (as shown in the code snippet above). This
* creates an instance of the class that can be used to initialize the tree and tree store. In the following code
* snippet we define a new interface called <code>DataProperties</code> that extends the <code>PropertyAccess</code>
* interface and is parameterized with <code>Data</code>, a Plain Old Java Object (POJO).
* <p/>
*
* <pre>
  public interface DataProperties extends PropertyAccess<Data> {
    &#64;Path("name")
    ModelKeyProvider<Data> key();
    ValueProvider&lt;Data, String> name();
    ValueProvider&lt;Data, String> value();
  }

  public class Data {
    private String name;
    private String value;

    public Data(String name, String value) {
      super();
      this.name = name;
      this.value = value;
    }
    public String getName() {
      return name;
    }
    public String getValue() {
      return value;
    }
    public void setName(String name) {
      this.name = name;
    }
    public void setValue(String value) {
      this.value = value;
    }
  }
* </pre>
* <p/>
* To enable drag and drop for a tree grid, add the following:
* <p/>
*
* <pre>
    new TreeGridDragSource<Data>(tg);
    TreeGridDropTarget<Data> dt = new TreeGridDropTarget<Data>(tg);
    dt.setFeedback(Feedback.BOTH);
* </pre>
* <p/>
* To add reordering support to the drag and drop, include:
*
* <pre>
    dt.setAllowSelfAsSource(true);
* </pre>
* <p/>
*
* @param <M> the model type
*/
public class TreeGrid<M> extends Grid<M> implements HasBeforeCollapseItemHandlers<M>, HasCollapseItemHandlers<M>,
    HasBeforeExpandItemHandlers<M>, HasExpandItemHandlers<M> {

  public static class TreeGridNode<M> extends TreeNode<M> {

    protected TreeGridNode(String modelId, M m, String domId) {
      super(modelId, m, domId);
    }

    @Override
    public void clearElements() {
      super.clearElements();
      setContainerElement(null);
      setElContainer(null);
      element = null;
    }

  }

  protected boolean filtering;
  protected TreeLoader<M> loader;
  protected Map<String, TreeNode<M>> nodes = new FastMap<TreeNode<M>>();
  protected Map<String, TreeNode<M>> nodesByDomId = new FastMap<TreeNode<M>>();
  protected StoreHandlers<M> storeHandler = new StoreHandlers<M>() {

    @Override
    public void onAdd(StoreAddEvent<M> event) {
      TreeGrid.this.onAdd(event);

    }

    @Override
    public void onClear(StoreClearEvent<M> event) {
      TreeGrid.this.onClear(event);

    }

    @Override
    public void onDataChange(StoreDataChangeEvent<M> event) {
      TreeGrid.this.onDataChange(event.getParent());

    }

    @Override
    public void onFilter(StoreFilterEvent<M> event) {
      TreeGrid.this.onFilter(event);

    }

    @Override
    public void onRecordChange(StoreRecordChangeEvent<M> event) {
      TreeGrid.this.onRecordChange(event);

    }

    @Override
    public void onRemove(StoreRemoveEvent<M> event) {
      TreeGrid.this.onRemove((TreeStoreRemoveEvent<M>) event);

    }

    @Override
    public void onSort(StoreSortEvent<M> event) {
      TreeGrid.this.onSort(event);

    }

    @Override
    public void onUpdate(StoreUpdateEvent<M> event) {
      TreeGrid.this.onUpdate(event);

    }
  };

  protected HandlerRegistration storeHandlerRegistration;
  protected TreeGridView<M> treeGridView;
  protected TreeStore<M> treeStore;
  private final GridAppearance appearance;

  private boolean autoLoad, autoExpand;
  private boolean caching = true;
  private boolean expandOnFilter = true;
  private IconProvider<M> iconProvider;
  private TreeStyle style = new TreeStyle();
  private TreeAppearance treeAppearance;
  private ColumnConfig<M, ?> treeColumn;
  private boolean expandOnDoubleClick = true;

  /**
   * Creates a new tree grid.
   *
   * @param store the tree store
   * @param cm the column model
   * @param treeColumn the tree column
   */
  public TreeGrid(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn) {
    this(store, cm, treeColumn, GWT.<GridAppearance> create(GridAppearance.class));
  }

  /**
   * Creates a new tree grid.
   *
   * @param store the tree store
   * @param cm the column model
   * @param treeColumn the tree column
   * @param appearance the grid appearance
   */
  public TreeGrid(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn, GridAppearance appearance) {
    this(store, cm, treeColumn, appearance, GWT.<TreeAppearance> create(TreeAppearance.class));
  }

  /**
   * Creates a new tree grid.
   *
   * @param store the tree store
   * @param cm the column model
   * @param treeColumn the tree column
   * @param appearance the grid appearance
   * @param treeAppearance the tree appearance
   */
  public TreeGrid(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn, GridAppearance appearance,
      TreeAppearance treeAppearance) {
    this.appearance = appearance;
    this.treeAppearance = treeAppearance;

    disabledStyle = null;

    SafeHtmlBuilder builder = new SafeHtmlBuilder();
    this.appearance.render(builder);

    setElement((Element) XDOM.create(builder.toSafeHtml()));
    getElement().makePositionable();

    // Do not remove, this is being used in Grid.css
    addStyleName("x-treegrid");

    getElement().setTabIndex(0);
    getElement().setAttribute("hideFocus", "true");

    this.cm = cm;
    setTreeColumn(treeColumn);
    this.treeStore = store;
    this.store = createListStore();

    setSelectionModel(new TreeGridSelectionModel<M>());

    disabledStyle = null;
    storeHandlerRegistration = treeStore.addStoreHandlers(storeHandler);

    setView(new TreeGridView<M>(appearance));
    setAllowTextSelection(false);

    sinkCellEvents();
  }

  @Override
  public HandlerRegistration addBeforeCollapseHandler(BeforeCollapseItemHandler<M> handler) {
    return addHandler(handler, BeforeCollapseItemEvent.getType());
  }

  @Override
  public HandlerRegistration addBeforeExpandHandler(BeforeExpandItemHandler<M> handler) {
    return addHandler(handler, BeforeExpandItemEvent.getType());
  }

  @Override
  public HandlerRegistration addCollapseHandler(CollapseItemHandler<M> handler) {
    return addHandler(handler, CollapseItemEvent.getType());
  }

  @Override
  public HandlerRegistration addExpandHandler(ExpandItemHandler<M> handler) {
    return addHandler(handler, ExpandItemEvent.getType());
  }

  /**
   * Collapses all nodes.
   */
  public void collapseAll() {
    for (M child : treeStore.getRootItems()) {
      setExpanded(child, false, true);
    }
  }

  /**
   * Expands all nodes.
   */
  public void expandAll() {
    for (M child : treeStore.getRootItems()) {
      setExpanded(child, true, true);
    }
  }

  /**
   * Returns the tree node for the given target.
   *
   * @param target the target element
   * @return the tree node or null if no match
   */
  public TreeNode<M> findNode(Element target) {
    Element row = (Element) getView().findRow(target);
    if (row != null) {
      XElement item = XElement.as(row).selectNode(treeAppearance.itemSelector());
      if (item != null) {
        String id = item.getId();
        TreeNode<M> node = nodesByDomId.get(id);
        return node;
      }
    }
    return null;
  }

  /**
   * Returns the grid appearance.
   *
   * @return the grid appearance
   */
  public GridAppearance getAppearance() {
    return appearance;
  }

  /**
   * Returns the model icon provider.
   *
   * @return the icon provider
   */
  public IconProvider<M> getIconProvider() {
    return iconProvider;
  }

  /**
   * Returns the tree style.
   *
   * @return the tree style
   */
  public TreeStyle getStyle() {
    return style;
  }

  /**
   * Returns the tree appearance.
   *
   * @return the tree appearance
   */
  public TreeAppearance getTreeAppearance() {
    return treeAppearance;
  }

  /**
   * Returns the column that represents the tree nodes.
   *
   * @return the tree column
   */
  public ColumnConfig<M, ?> getTreeColumn() {
    return treeColumn;
  }

  /**
   * Returns the tree loader.
   *
   * @return the tree loader or null if not specified
   */
  public TreeLoader<M> getTreeLoader() {
    return loader;
  }

  /**
   * Returns the tree's tree store.
   *
   * @return the tree store
   */
  public TreeStore<M> getTreeStore() {
    return treeStore;
  }

  /**
   * Returns the tree's view.
   *
   * @return the view
   */
  public TreeGridView<M> getTreeView() {
    return treeGridView;
  }

  /**
   * Returns true if auto expand is enabled.
   *
   * @return the auto expand state
   */
  public boolean isAutoExpand() {
    return autoExpand;
  }

  /**
   * Returns true if auto load is enabled.
   *
   * @return the auto load state
   */
  public boolean isAutoLoad() {
    return autoLoad;
  }

  /**
   * Returns true when a loader is queried for it's children each time a node is expanded. Only applies when using a
   * loader with the tree store.
   *
   * @return true if caching
   */
  public boolean isCaching() {
    return caching;
  }

  /**
   * Returns true if the model is expanded.
   *
   * @param model the model
   * @return true if expanded
   */
  public boolean isExpanded(M model) {
    TreeNode<M> node = findNode(model);
    return node != null && node.isExpanded();
  }

  /**
   * Returns the expand on double click state.
   *
   * @return the expand on double click state
   */
  public boolean isExpandOnDoubleClick() {
    return expandOnDoubleClick;
  }

  /**
   * Returns the if expand all and collapse all is enabled on filter changes.
   *
   * @return the expand all collapse all state
   */
  public boolean isExpandOnFilter() {
    return expandOnFilter;
  }

  /**
   * Returns true if the model is a leaf node. The leaf state allows a tree item to specify if it has children before
   * the children have been realized.
   *
   * @param model the model
   * @return the leaf state
   */
  public boolean isLeaf(M model) {
    return !hasChildren(model);
  }

  @Override
  public void reconfigure(ListStore<M> store, ColumnModel<M> cm) {
    throw new UnsupportedOperationException("Please call the other reconfigure method");
  }

  public void reconfigure(TreeStore<M> store, ColumnModel<M> cm, ColumnConfig<M, ?> treeColumn) {
    if (isLoadMask()) {
      mask(DefaultMessages.getMessages().loadMask_msg());
    }
    this.store.clear();

    nodes.clear();
    nodesByDomId.clear();

    this.store = createListStore();

    if (storeHandlerRegistration != null) {
      storeHandlerRegistration.removeHandler();
    }

    treeStore = store;
    if (treeStore != null) {
      storeHandlerRegistration = treeStore.addStoreHandlers(storeHandler);
    }

    treeGridView.initData(this.store, cm);

    this.cm = cm;
    setTreeColumn(treeColumn);
    // rebind the sm
    setSelectionModel(sm);
    if (isViewReady()) {
      view.refresh(true);
      doInitialLoad();
    }

    if (isLoadMask()) {
      unmask();
    }
  }

  /**
   * Refreshes the data for the given model.
   *
   * @param model the model to be refreshed
   */
  public void refresh(M model) {
    TreeNode<M> node = findNode(model);
    if (viewReady && node != null) {
      treeGridView.onIconStyleChange(node, calculateIconStyle(model));
      treeGridView.onJointChange(node, calculateJoint(model));
    }
  }

  /**
   * If set to true, all non leaf nodes will be expanded automatically (defaults to false).
   *
   * @param autoExpand the auto expand state to set.
   */
  public void setAutoExpand(boolean autoExpand) {
    this.autoExpand = autoExpand;
  }

  /**
   * Sets whether all children should automatically be loaded recursively (defaults to false). Useful when the tree must
   * be fully populated when initially rendered.
   *
   * @param autoLoad true to auto load
   */
  public void setAutoLoad(boolean autoLoad) {
    this.autoLoad = autoLoad;
  }

  /**
   * Sets whether the children should be cached after first being retrieved from the store (defaults to true). When
   * <code>false</code>, a load request will be made each time a node is expanded.
   *
   * @param caching the caching state
   */
  public void setCaching(boolean caching) {
    this.caching = caching;
  }

  /**
   * Sets the item's expand state.
   *
   * @param model the model
   * @param expand true to expand
   */
  public void setExpanded(M model, boolean expand) {
    setExpanded(model, expand, false);
  }

  /**
   * Sets the item's expand state.
   *
   * @param model the model
   * @param expand true to expand
   * @param deep true to expand all children recursively
   */
  public void setExpanded(M model, boolean expand, boolean deep) {
    if (expand) {
      // make parents visible
      List<M> list = new ArrayList<M>();
      M p = model;
      while ((p = treeStore.getParent(p)) != null) {
        TreeNode<M> n = findNode(p);
        if (n == null || !n.isExpanded()) {
          list.add(p);
        }
      }
      for (int i = list.size() - 1; i >= 0; i--) {
        M item = list.get(i);
        setExpanded(item, expand, false);
      }
    }

    TreeNode<M> node = findNode(model);
    if (node == null) {
      assert !expand;
      return;
    }
    if (expand) {
      if (!isLeaf(model)) {
        // if we are loading, ignore it
        if (node.isLoading()) {
          return;
        }
        // if we have a loader and node is not loaded make
        // load request and exit method
        if (!node.isExpanded() && loader != null && (!node.isLoaded() || !caching) && !filtering) {
          treeStore.removeChildren(model);
          node.setExpand(true);
          node.setExpandDeep(deep);
          node.setLoading(true);
          treeGridView.onLoading(node);
          loader.loadChildren(model);
          return;
        }
        if (!node.isExpanded() && fireCancellableEvent(new BeforeExpandItemEvent<M>(model))) {
          node.setExpanded(true);

          if (!node.isChildrenRendered()) {
            renderChildren(model, false);
            node.setChildrenRendered(true);
          }
          // expand
          treeGridView.expand(node);
          fireEvent(new ExpandItemEvent<M>(model));
        }

        if (deep) {
          setExpandChildren(model, true);
        }
      }
    } else {
      if (node.isExpanded() && fireCancellableEvent(new BeforeCollapseItemEvent<M>(model))) {
        node.setExpanded(false);
        // collapse
        treeGridView.collapse(node);
        fireEvent(new CollapseItemEvent<M>(model));
      }
      if (deep) {
        setExpandChildren(model, false);
      }
    }
  }

  /**
   * Determines if the nodes should be expanded and collapsed on double clicks (defaults to {@code true}). Set to false
   * when using two clicks to edit inline editing.
   *
   * @param expandOnDoubleClick true to expand and collapse on double clicks
   */
  public void setExpandOnDoubleClick(boolean expandOnDoubleClick) {
    this.expandOnDoubleClick = expandOnDoubleClick;
  }

  /**
   * Sets whether the tree should expand all and collapse all when filters are applied (defaults to true).
   *
   * @param expandOnFilter true to expand and collapse on filter changes
   */
  public void setExpandOnFilter(boolean expandOnFilter) {
    this.expandOnFilter = expandOnFilter;
  }

  /**
   * Sets the tree's model icon provider which provides the icon style for each model.
   *
   * @param iconProvider the icon provider
   */
  public void setIconProvider(IconProvider<M> iconProvider) {
    this.iconProvider = iconProvider;
  }

  /**
   * Sets the item's leaf state. The leaf state allows control of the expand icon before the children have been
   * realized.
   *
   * @param model the model
   * @param leaf the leaf state
   */
  public void setLeaf(M model, boolean leaf) {
    TreeNode<M> t = findNode(model);
    if (t != null) {
      t.setLeaf(leaf);
    }
  }

  /**
   * Sets the tree loader.
   *
   * @param treeLoader the tree loader
   */
  public void setTreeLoader(TreeLoader<M> treeLoader) {
    this.loader = treeLoader;
  }

  @Override
  public void setView(GridView<M> view) {
    assert view instanceof TreeGridView : "The view for a TreeGrid has to be an instance of TreeGridView";
    super.setView(view);
    treeGridView = (TreeGridView<M>) view;
  }

  /**
   * Toggles the model's expand state.
   *
   * @param model the model
   */
  public void toggle(M model) {
    TreeNode<M> node = findNode(model);
    if (node != null) {
      setExpanded(model, !node.isExpanded());
    }
  }

  protected ImageResource calculateIconStyle(M model) {
    ImageResource style = null;
    if (iconProvider != null) {
      ImageResource iconStyle = iconProvider.getIcon(model);
      if (iconStyle != null) {
        return iconStyle;
      }
    }
    TreeStyle ts = getStyle();
    if (!isLeaf(model)) {
      if (isExpanded(model)) {
        style = ts.getNodeOpenIcon() != null ? ts.getNodeOpenIcon() : treeAppearance.openNodeIcon();
      } else {
        style = ts.getNodeCloseIcon() != null ? ts.getNodeCloseIcon() : treeAppearance.closeNodeIcon();
      }
    } else {
      style = ts.getLeafIcon();
    }
    return style;
  }

  protected Joint calculateJoint(M model) {
    if (model == null) {
      return Joint.NONE;
    }
    TreeNode<M> node = findNode(model);
    Joint joint = Joint.NONE;
    if (node == null) {
      return joint;
    }
    if (!isLeaf(model)) {
      boolean children = true;

      if (node.isExpanded()) {
        joint = children ? Joint.EXPANDED : Joint.NONE;
      } else {
        joint = children ? Joint.COLLAPSED : Joint.NONE;
      }
    }
    return joint;
  }

  protected ListStore<M> createListStore() {
    return new ListStore<M>(treeStore.getKeyProvider()) {
      @Override
      public Record getRecord(M model) {
        return treeStore.getRecord(model);
      }

      @Override
      public boolean hasRecord(M model) {
        return treeStore.hasRecord(model);
      }
    };
  }

  /**
   * Returns the index in the liststore of the last child in the subtree starting at the given model. The given model
   * (or its children) do not need to already be in the list store, but all other items do. This makes this method
   * useful for finding where a model and its subtree belong.
   *
   * @param model the model used to find the last open child index.
   * @return returns the last open child index.
   */
  protected int findLastOpenChildIndex(M model) {
    // make sure this node is visible
    assert treeStore.indexOf(model) != -1;

    // Find the next visible node parallel to or above this one
    M outer = model;
    M nextSibling = treeStore.getNextSibling(outer);
    while (nextSibling == null) {
      outer = treeStore.getParent(outer);
      // check if we are at the root
      if (outer == null) {
        break;
      }
      nextSibling = treeStore.getNextSibling(outer);
    }
    if (outer == null) {
      // hit the root, return the last element
      return store.size() - 1;
    }
    // otherwise, we've got the next element - get _its_ index in the liststore, and return that minus one
    return store.indexOf(nextSibling) - 1;

  }

  protected TreeNode<M> findNode(M m) {
    if (m == null) return null;
    return nodes.get(generateModelId(m));
  }

  protected String generateModelId(M m) {
    return treeStore.getKeyProvider().getKey(m);
  }

  protected boolean hasChildren(M model) {
    TreeNode<M> node = findNode(model);
    if (loader != null && node != null && !node.isLoaded()) {
      return loader.hasChildren(node.getModel());
    }
    if (node != null && (!node.isLeaf() || treeStore.hasChildren(node.getModel()))) {
      return true;
    }
    return false;
  }

  protected void onAdd(StoreAddEvent<M> se) {
    if (viewReady) {
      for (M child : se.getItems()) {
        register(child);
      }
      M p = treeStore.getParent(se.getItems().get(0));
      final int index;
      if (p == null) {
        // adding to root
        if (se.getIndex() == 0) {
          // no need to adjust index, since we're adding to the top
          index = se.getIndex();
        } else {
          // adjust index to add to the flattened tree
          index = findLastOpenChildIndex(se.getItems().get(se.getItems().size() - 1)) + 1;
        }
      } else {
        // adding to an existing node
        TreeNode<M> node = findNode(p);
        if (node == null) {
          // node's parent collapsed
          return;
        }
        if (!node.isExpanded()) {
          refresh(p);
          return;
        }
        if (se.getIndex() == 0) {
          // inserting at the top of this node, so adjust based on the parent's index
          index = store.indexOf(p) + 1;
        } else {
          // adjust the index to add to the local flattened tree
          index = findLastOpenChildIndex(se.getItems().get(se.getItems().size() - 1)) + 1;
        }
        refresh(p);

      }
      store.addAll(index, se.getItems());
    }
  }

  @Override
  protected void onAfterRenderView() {
    super.onAfterRenderView();
    doInitialLoad();
  }

  protected void onClear(StoreClearEvent<M> event) {
    onDataChange(null);
  }

  @Override
  protected void onClick(Event event) {
    EventTarget eventTarget = event.getEventTarget();
    if (Element.is(eventTarget)) {

      M m = store.get(getView().findRowIndex(Element.as(eventTarget)));
      if (m != null) {
        TreeNode<M> node = findNode(m);
        if (node != null) {
          Element jointEl = treeGridView.getJointElement(node);
          if (jointEl != null && jointEl.isOrHasChild((Element.as(eventTarget)))) {
            toggle(m);
          } else {
            super.onClick(event);
          }
        }
      }
    } else {
      super.onClick(event);
    }
  }

  protected void onDataChange(M parent) {
    if (!viewReady) {
      return;
    }

    if (parent == null) {
      store.clear();
      nodes.clear();
      nodesByDomId.clear();
      renderChildren(null, autoLoad);
    } else {
      TreeNode<M> n = findNode(parent);
      if (n != null) {
        treeGridView.collapse(n);
        n.setExpanded(false);

        n.setLoaded(true);
        n.setLoading(false);
        renderChildren(parent, autoLoad);

        if (n.isExpand() && !isLeaf(parent)) {
          n.setExpand(false);
          boolean deep = n.isExpandDeep();
          n.setExpandDeep(false);
          boolean c = caching;
          caching = true;
          setExpanded(parent, true, deep);
          caching = c;
        } else {
          refresh(parent);
        }
      }
    }
  }

  @Override
  protected void onDoubleClick(Event e) {
    super.onDoubleClick(e);
    if (expandOnDoubleClick) {
      if (Element.is(e.getEventTarget())) {
        int i = getView().findRowIndex(Element.as(e.getEventTarget()));
        M m = store.get(i);
        if (m != null) {
          toggle(m);
        }
      }
    }
  }

  protected void onFilter(StoreFilterEvent<M> se) {
    onDataChange(null);
    if (expandOnFilter && treeStore.isFiltered()) {
      expandAll();
    }
  }

  protected void onRecordChange(StoreRecordChangeEvent<M> event) {
    store.update(event.getRecord().getModel());

  }

  protected void onRemove(TreeStoreRemoveEvent<M> event) {
    if (viewReady) {
      unregister(event.getItem());
      store.remove(event.getItem());
      for (M child : event.getChildren()) {
        unregister(child);
        store.remove(child);
      }
      TreeNode<M> p = findNode(event.getParent());
      if (p != null && p.isExpanded() && treeStore.getChildCount(p.getModel()) == 0) {
        setExpanded(p.getModel(), false);
      } else if (p != null && treeStore.getChildCount(p.getModel()) == 0) {
        refresh(event.getParent());
      }
    }
  }

  protected void onSort(StoreSortEvent<M> event) {
    onDataChange(null);

  }

  protected void onUpdate(StoreUpdateEvent<M> se) {
    for (M m : se.getItems()) {
      // looking up by key instead of by index to allow for different model instance, but no equals() equality
      if (store.findModelWithKey(store.getKeyProvider().getKey(m)) != null) {
        store.update(m);
      }
    }
  }

  protected String register(M m) {
    String id = generateModelId(m);
    if (!nodes.containsKey(id)) {
      String domId = XDOM.getUniqueId();
      TreeGridNode<M> node = new TreeGridNode<M>(id, m, domId);
      nodes.put(id, node);
      nodesByDomId.put(domId, node);
    }
    return id;
  }

  protected void renderChildren(M parent, boolean auto) {
    List<M> children = parent == null ? treeStore.getRootItems() : treeStore.getChildren(parent);

    for (M child : children) {
      register(child);
    }

    if (parent == null && children.size() > 0) {
      store.addAll(children);
    }

    for (M child : children) {
      if (autoExpand) {
        final M c = child;
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
          @Override
          public void execute() {
            setExpanded(c, true);
          }
        });
      } else if (loader != null && autoLoad) {
        if (store.isFiltered() || (!auto)) {
          renderChildren(child, auto);
        } else {
          loader.loadChildren(child);
        }
      }
    }
  }

  protected void setTreeColumn(ColumnConfig<M, ?> treeColumn) {
    assert (treeColumn != null && cm.indexOf(treeColumn) != -1) : "treeColumn not found in ColumnModel";
    this.treeColumn = treeColumn;
  }

  protected void unregister(M m) {
    TreeNode<M> node = findNode(m);
    if (node != null) {
      node.clearElements();

      TreeNode<M> removed = nodes.remove(generateModelId(m));
      assert removed != null;
      nodesByDomId.remove(removed.getDomId());
    }
  }

  private void doInitialLoad() {
    if (treeStore.getRootItems().size() == 0 && loader != null) {
      loader.load();
    } else {
      renderChildren(null, false);
      if (autoExpand) {
        expandAll();
      }
    }

  }

  private void setExpandChildren(M m, boolean expand) {
    for (M child : treeStore.getChildren(m)) {
      setExpanded(child, expand, true);
    }
  }

}
TOP

Related Classes of com.sencha.gxt.widget.core.client.treegrid.TreeGrid$TreeGridNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.