/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.binder;
import java.util.ArrayList;
import java.util.List;
import com.extjs.gxt.ui.client.data.ModelComparer;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.TreeLoader;
import com.extjs.gxt.ui.client.event.CheckChangedEvent;
import com.extjs.gxt.ui.client.event.CheckChangedListener;
import com.extjs.gxt.ui.client.event.CheckProvider;
import com.extjs.gxt.ui.client.event.EventType;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.TreeEvent;
import com.extjs.gxt.ui.client.store.StoreEvent;
import com.extjs.gxt.ui.client.store.TreeStore;
import com.extjs.gxt.ui.client.store.TreeStoreEvent;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.DataListItem;
import com.extjs.gxt.ui.client.widget.tree.Tree;
import com.extjs.gxt.ui.client.widget.tree.TreeItem;
import com.extjs.gxt.ui.client.widget.treepanel.TreePanel;
/**
* A <code>StoreBinder</code> implementation for Trees.
*
* @param <M> the model type
*
* @deprecated see {@link TreePanel}
*/
public class TreeBinder<M extends ModelData> extends StoreBinder<TreeStore<M>, Tree, M> implements
CheckProvider<M> {
protected Tree tree;
protected TreeStore<M> store;
protected TreeLoader<M> loader;
protected String displayProperty;
protected boolean autoLoad;
private List<CheckChangedListener<M>> checkListeners;
private boolean silent;
private boolean caching = true;
private boolean expandOnFilter = true;
/**
* Creates a new store binder.
*
* @param tree the tree
* @param store the tree store
*/
public TreeBinder(final Tree tree, TreeStore<M> store) {
super(store, tree);
this.tree = tree;
this.store = store;
this.loader = store.getLoader();
}
public void addCheckListener(CheckChangedListener<M> listener) {
if (checkListeners == null) {
checkListeners = new ArrayList<CheckChangedListener<M>>();
}
if (!checkListeners.contains(listener)) {
checkListeners.add(listener);
}
}
@Override
public Component findItem(M model) {
ModelComparer<M> comparer = store.getModelComparer();
for (TreeItem item : tree.getAllItems()) {
if (comparer.equals(item.<M>getModel(), model)) {
return item;
}
}
return null;
}
public List<M> getCheckedSelection() {
List<M> selection = new ArrayList<M>();
for (TreeItem item : tree.getChecked()) {
selection.add(item.<M>getModel());
}
return selection;
}
/**
* Returns the display property.
*
* @return the display property
*/
public String getDisplayProperty() {
return displayProperty;
}
/**
* Returns the binder's tree.
*
* @return the tree
*/
public Tree getTree() {
return tree;
}
/**
* Returns the binder's tree store.
*
* @return the tree store
*/
public TreeStore<M> getTreeStore() {
return store;
}
/**
* Returns true if auto load is enabled.
*
* @return the auto load state
*/
public boolean isAutoLoad() {
return autoLoad;
}
/**
* Returns <code>true</code> if the binder is caching.
*
* @return the caching state
*/
public boolean isCaching() {
return caching;
}
public boolean isChecked(M model) {
DataListItem item = (DataListItem) findItem(model);
if (item != null) {
return item.isChecked();
}
return false;
}
/**
* 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;
}
public void removeCheckListener(CheckChangedListener<M> listener) {
if (checkListeners != null) {
checkListeners.remove(checkListeners);
}
}
/**
* Sets whether all children should automatically be loaded. Useful when using
* filters.
*
* @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>, the tree items will
* be removed when collapsed.
*
* @param caching the caching state
*/
public void setCaching(boolean caching) {
this.caching = caching;
}
public void setCheckedSelection(List<M> selection) {
silent = true;
for (TreeItem item : tree.getChecked()) {
if (item.isChecked()) {
item.setChecked(false);
}
}
for (M m : selection) {
TreeItem item = (TreeItem) findItem(m);
if (item != null) {
item.setChecked(true);
}
}
silent = false;
fireCheckChanged(getCheckedSelection());
}
/**
* Sets the display property name used to the item's text.
*
* @param displayProperty the property
*/
public void setDisplayProperty(String displayProperty) {
this.displayProperty = displayProperty;
}
/**
* 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;
}
protected void createAll() {
TreeItem root = tree.getRootItem();
root.removeAll();
List<M> list = store.getRootItems();
for (M element : list) {
TreeItem item = null;
if (store.isFiltered()) {
if (isOrDecendantSelected(null, element)) {
item = createItem(element);
}
} else {
item = createItem(element);
}
tree.getRootItem().add(item);
if (autoLoad && item != null) {
item.setData("force", true);
loadChildren(item, false);
}
}
if (isAutoSelect() && list.size() > 0) {
setSelection(list.get(0));
}
}
protected TreeItem createItem(M model) {
TreeItem item = new TreeItem();
update(item, model);
if (loader != null) {
item.setLeaf(!loader.hasChildren(model));
} else {
item.setLeaf(!hasChildren(model));
}
setModel(item, model);
return item;
}
protected void fireCheckChanged(List<M> selection) {
if (checkListeners != null) {
CheckChangedEvent<M> evt = new CheckChangedEvent<M>(this, selection);
for (CheckChangedListener<M> listener : checkListeners) {
listener.checkChanged(evt);
}
}
}
@Override
protected List<M> getSelectionFromComponent() {
List<TreeItem> selItems = tree.getSelectedItems();
List<M> selected = new ArrayList<M>();
for (TreeItem item : selItems) {
selected.add(item.<M>getModel());
}
return selected;
}
protected boolean hasChildren(M parent) {
return store.getChildCount(parent) != 0;
}
@Override
protected void hook() {
super.hook();
component.setData("binder", true);
component.addListener(Events.CheckChange, new Listener<TreeEvent>() {
public void handleEvent(TreeEvent be) {
if (!silent) {
fireCheckChanged(getCheckedSelection());
}
}
});
Listener<TreeEvent> l = new Listener<TreeEvent>() {
public void handleEvent(TreeEvent te) {
EventType type = te.getType();
if (type == Events.BeforeExpand) {
onBeforeExpand(te);
} else if (type == Events.Collapse) {
onCollapse(te);
}
}
};
component.addListener(Events.BeforeExpand, l);
component.addListener(Events.Collapse, l);
}
protected void loadChildren(final TreeItem item, boolean expand) {
if (loader == null) {
M model = item.<M>getModel();
List<M> children = store.getChildren(model);
renderChildren(model, children);
return;
}
// if there is an async call out for my children already, Make
// sure not to make another call and load the same items twice
if (!item.isEnabled()) {
return;
}
item.disable();
if (item.isRendered() && !item.isRoot()) {
item.getUI().onLoadingChange(true);
}
if (expand) {
item.setData("expand", expand);
}
loader.loadChildren(item.<M>getModel());
}
@Override
protected void onAdd(StoreEvent<M> se) {
TreeStoreEvent<M> tse = (TreeStoreEvent<M>) se;
M parent = tse.getParent();
TreeItem item = null;
if (parent == null) {
item = tree.getRootItem();
} else {
item = (TreeItem) findItem(parent);
}
if (item == null) {
return;
}
if (item.isRendered() && !item.isRoot()) {
item.updateJointStyle();
item.updateIconStyle();
}
if (item.isRoot() || item.getData("loaded") != null) {
List<M> children = tse.getChildren();
if (children.size() > 0) {
item.setLeaf(false);
}
for (int i = children.size() - 1; i >= 0; i--) {
item.add(createItem(children.get(i)), tse.getIndex());
}
} else if (item.getData("loaded") == null) {
if (hasChildren(item.<M>getModel())) {
item.setLeaf(false);
if (item.isRendered()) {
item.updateJointStyle();
}
}
}
}
protected void onBeforeExpand(TreeEvent te) {
TreeItem item = te.getItem();
if (item.getData("loaded") == null) {
item.setData("expand", true);
te.setCancelled(true);
loadChildren(item, true);
}
}
protected void onCollapse(TreeEvent te) {
if (!caching) {
TreeItem item = te.getItem();
store.removeAll(item.<M>getModel());
item.setData("loaded", null);
item.setLeaf(false);
markChildrenRendered(item, false);
}
}
@Override
protected void onDataChanged(StoreEvent<M> se) {
super.onDataChanged(se);
TreeStoreEvent<M> te = (TreeStoreEvent<M>) se;
if (te.getParent() == null) {
createAll();
} else {
onRenderChildren(te);
}
}
@Override
protected void onFilter(StoreEvent<M> se) {
filterItems(tree.getRootItem());
boolean animated = tree.getAnimate();
tree.setAnimate(false);
if (store.isFiltered() && expandOnFilter) {
tree.expandAll();
} else if (!store.isFiltered() && expandOnFilter) {
tree.collapseAll();
}
tree.setAnimate(animated);
}
@Override
protected void onRemove(StoreEvent<M> se) {
TreeStoreEvent<M> tse = (TreeStoreEvent<M>) se;
TreeItem p = (TreeItem) findItem(tse.getParent());
if (p == null) p = tree.getRootItem();
p.remove((TreeItem) findItem(tse.getChild()));
}
protected void onRenderChildren(TreeStoreEvent<M> te) {
renderChildren(te.getParent(), te.getChildren());
}
@Override
protected void onSort(StoreEvent<M> se) {
}
@Override
protected void onUpdate(StoreEvent<M> se) {
update(se.getModel());
}
@Override
protected void removeAll() {
tree.removeAll();
}
protected void renderChildren(M parent, List<M> children) {
TreeItem p = (TreeItem) findItem(parent);
p.setData("loaded", true);
p.enable();
boolean leaf = p.isLeaf();
p.removeAll();
p.setLeaf(leaf);
if (p.isRendered()) {
p.getUI().onLoadingChange(false);
}
boolean f = p.getData("force") != null;
p.setData("force", null);
for (M child : children) {
TreeItem item = createItem(child);
p.add(item);
if (f) {
item.setData("force", true);
loadChildren(item, false);
}
}
if (!f && p.hasChildren() && p.getData("expand") != null) {
p.setExpanded(true);
} else if (p.isLeaf()) {
p.setLeaf(true);
}
if (p.isRendered()) {
p.updateJointStyle();
}
fireEvent(Events.Refresh);
}
@Override
protected void setSelectionFromProvider(List<M> selection) {
List<TreeItem> selected = new ArrayList<TreeItem>();
for (M model : selection) {
selected.add((TreeItem) findItem(model));
}
tree.setSelectedItems(selected);
}
@Override
protected void update(M model) {
update((TreeItem) findItem(model), model);
}
protected void update(TreeItem item, M model) {
if (item != null) {
setModel(item, model);
String txt = getTextValue(model, displayProperty);
if (txt == null && displayProperty != null) {
txt = model.get(displayProperty);
} else if (txt == null) {
txt = model.toString();
}
String icon = getIconValue(model, displayProperty);
String style = (styleProvider == null) ? null : styleProvider.getStringValue(model,
displayProperty);
item.setTextStyle(style);
item.setText(txt);
item.setIconStyle(icon);
}
}
private void filterItems(TreeItem item) {
if (item.isRoot() || isOrDecendantSelected(null, item.<M>getModel())) {
item.setVisible(true);
int count = item.getItemCount();
for (int i = 0; i < count; i++) {
filterItems(item.getItem(i));
}
} else {
item.setVisible(false);
}
}
private boolean isOrDecendantSelected(M parent, M model) {
if (!isFiltered(parent, model)) {
return true;
}
TreeItem item = (TreeItem) findItem(model);
if (item != null) {
for (TreeItem child : item.getItems()) {
boolean result = isOrDecendantSelected(model, child.<M>getModel());
if (result) {
return true;
}
}
}
return false;
}
private native void markChildrenRendered(TreeItem item, boolean rendered) /*-{
item.@com.extjs.gxt.ui.client.widget.tree.TreeItem::childrenRendered = rendered;
}-*/;
}