/* * 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.widget; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.extjs.gxt.ui.client.core.El; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.ContainerEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.widget.layout.FlowLayout; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; /** * Class for any {@link BoxComponent} that can contain other components. * Containers handle the basic behavior of containing components, namely * managing, attaching, and detaching the child widgets. * * <p/> * When children are added to a container they are not physically added to the * DOM of the container. Subclasses are responsible for connecting the child * components. * * <p/> * Container does not define a root element. setElement must be called by any * subclass to ensure the container has an element. * * <dl> * <dt><b>Events:</b></dt> * * <dd><b>BeforeAdd</b> : ContainerEvent(container, item, index)<br> * <div>Fires before a item is added or inserted. Listeners can cancel the * action by calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>container : this</li> * <li>item : the component being added</li> * <li>index : the index at which the component will be added</li> * </ul> * </dd> * * <dd><b>BeforeRemove</b> : ContainerEvent(container, item)<br> * <div>Fires before a item is removed. Listeners can cancel the action by * calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>container : this</li> * <li>item : the component being removed</li> * </ul> * </dd> * * <dd><b>Add</b> : ContainerEvent(container, item, index)<br> * <div>Fires after a item has been added or inserted.</div> * <ul> * <li>container : this</li> * <li>item : the item that was added</li> * <li>index : the index at which the item will be added</li> * </ul> * </dd> * * <dd><b>Remove</b> : ContainerEvent(container, item)<br> * <div>Fires after a item has been removed.</div> * <ul> * <li>container : this</li> * <li>item : the item being removed</li> * </ul> * </dd> * </dl> * * <dl> * <dt>Inherited Events:</dt> * <dd>BoxComponent Move</dd> * <dd>BoxComponent Resize</dd> * <dd>Component Enable</dd> * <dd>Component Disable</dd> * <dd>Component BeforeHide</dd> * <dd>Component Hide</dd> * <dd>Component BeforeShow</dd> * <dd>Component Show</dd> * <dd>Component Attach</dd> * <dd>Component Detach</dd> * <dd>Component BeforeRender</dd> * <dd>Component Render</dd> * <dd>Component BrowserEvent</dd> * <dd>Component BeforeStateRestore</dd> * <dd>Component StateRestore</dd> * <dd>Component BeforeStateSave</dd> * <dd>Component SaveState</dd> * </dl> * * @param <T> the child component type */ public abstract class Container<T extends Component> extends BoxComponent { /** * True to attach the container's children (defaults to true). */ protected boolean attachChildren = true; /** * False to disable the container's layout, stopping it from executing * (defaults to false). */ protected boolean enableLayout; /** * True to execute the container's layout when children are inserted and * removed (defaults to false). */ protected boolean layoutOnChange; protected boolean layoutExecuted; protected boolean layoutNeeded = true; protected boolean layoutOnAttach = true; private List<T> items; private Layout layout; /** * Creates a new container. */ public Container() { items = new ArrayList<T>(); } @Override public void disable() { super.disable(); for (Component c : items) { c.disable(); } } @Override public void enable() { super.enable(); for (Component c : items) { c.enable(); } } /** * Returns the component whose element, or child element, matches the given * element. * * @param elem the element * @return the matching component or <code>null</code> if no match */ public T findItem(Element elem) { for (T c : items) { if (DOM.isOrHasChild(c.getElement(), elem)) { return c; } } return null; } /** * Returns the item at the given index or null if index out of bounds. * * @param index the index * @return the item */ public T getItem(int index) { return index < items.size() ? items.get(index) : null; } /** * Returns the item with the specified item id. * * @param itemId the item id * @return the button or <code>null</code> if no match */ public T getItemByItemId(String itemId) { for (T item : getItems()) { if (item.getItemId().equals(itemId)) { return item; } } return null; } /** * Returns the number of children. * * @return the component count */ public int getItemCount() { return items.size(); } /** * Returns the child items. * * @return the children */ public List<T> getItems() { return items; } /** * Returns the container's layout target. Only applies to container's with * layouts. * * @return the layout target */ public El getLayoutTarget() { return el(); } /** * Returns the widget at the given index. If the child is a WidgetComponent, * the wrapped widget is returned. * * @param index the index * @return the widget */ public Widget getWidget(int index) { Component c = getItem(index); if (c != null && c instanceof WidgetComponent) { return ((WidgetComponent) c).getWidget(); } return c; } /** * Returns the index of the item. * * @param item the item * @return the index */ public int indexOf(T item) { return items.indexOf(item); } /** * Returns an iterator over the container's children. * * @return an iterator */ public Iterator<T> iterator() { return items.iterator(); } /** * Removes all the container's items. * * @return true if all items where removed */ public boolean removeAll() { return removeAll(false); } /** * Scrolls the item into view. * * @param item the item */ public void scrollIntoView(T item) { if (rendered) { item.el().scrollIntoView(el().dom, false); } } /** * Adds a item to the container. Fires the <i>BeforeAdd</i> event before * inserting, then fires the <i>Add</i> event after the widget has been * inserted. * * @param item the item to be added */ protected boolean add(T item) { return insert(item, getItemCount()); } protected int adjustIndex(T child, int beforeIndex) { if (child.getParent() == this) { int idx = indexOf(child); if (idx < beforeIndex) { beforeIndex--; } } return beforeIndex; } /** * Sets the child's parent to this container. In order to support lazy * rendering this method uses JSNI to simply set the child parent without * effecting the attached state of the child. * * @param child the child widget */ protected void adopt(T child) { setParent(this, child); } @SuppressWarnings("unchecked") protected ContainerEvent createContainerEvent(T item) { return new ContainerEvent(this, item); } @Override protected void doAttachChildren() { if (attachChildren) { for (T item : items) { if (item.isRendered() && !item.isAttached()) { item.onAttach(); } } } } @Override protected void doDetachChildren() { if (attachChildren) { for (T item : items) { if (item.isRendered() && item.isAttached()) { item.onDetach(); } } } } protected boolean doLayout() { return doLayout(false); } protected boolean doLayout(boolean force) { if (!enableLayout) { return false; } if (layout == null) { setLayout(new FlowLayout()); } // execute the layout if (force || layoutNeeded) { layoutExecuted = true; layout.layout(); if (isVisible()) { layoutNeeded = false; } } for (Component c : items) { if (attachChildren && c.isRendered()) { ComponentHelper.doAttach(c); } if (c instanceof Composite) { c = ((Composite) c).getComponent(); } if (c instanceof LayoutContainer) { ((LayoutContainer) c).layout(force); } else if (c instanceof Container) { Container<?> con = (Container<?>) c; if (con.layout != null) { con.layout(force); } } else { c.recalculate(); } } onAfterLayout(); fireEvent(Events.AfterLayout, createContainerEvent(null)); return true; } protected Layout getLayout() { return layout; } /** * Adds a item into the container. Fires the <i>BeforeAdd</i> event before * inserting, then fires the <i>Add</i> event after the widget has been * inserted. * * @param item the item to insert * @param index the insert location */ @SuppressWarnings("unchecked") protected boolean insert(T item, int index) { ContainerEvent containerEvent = createContainerEvent(item); containerEvent.setIndex(index); if (fireEvent(Events.BeforeAdd, containerEvent)) { ComponentEvent componentEvent = item.createComponentEvent(null); if (item.fireEvent(Events.BeforeAdopt, componentEvent)) { index = adjustIndex(item, index); item.removeFromParent(); items.add(index, item); onInsert(item, index); adopt(item); item.fireEvent(Events.Adopt, componentEvent); fireEvent(Events.Add, containerEvent); layoutNeeded = true; if (isRendered() && layoutOnChange) { layout(); } return true; } } return false; } protected boolean isLayoutNeeded() { return layoutNeeded; } /** * Executes the container's layout. If a layout has not been set a * <code>FlowLayout</code> will be used. * * @return true if layout was executed. */ protected boolean layout() { return layout(false); } /** * * Executes the container's layout. If a layout has not been set a * <code>FlowLayout</code> will be used. * * @param force true to force the layout call, also if caching things it is * not needed * @return true if layout was executed. */ protected boolean layout(boolean force) { if (!rendered) { layoutOnAttach = true; return false; } return doLayout(force); } protected void onAfterLayout() { sync(true); } @Override protected void onAttach() { super.onAttach(); if (!layoutExecuted && layoutOnAttach && !(getParent() instanceof Container)) { layout(); } } protected void onInsert(T item, int index) { if (isAutoHeight() || isAutoWidth()) { sync(true); } } protected void onRemove(T item) { if (isAutoHeight() || isAutoWidth()) { sync(true); } } @Override protected void onResize(int width, int height) { super.onResize(width, height); layoutNeeded = true; for (Component c : items) { if (c instanceof Container) { ((Container<?>) c).layoutNeeded = true; } } } protected final void orphan(T child) { assert (child.getParent() == this); if (child.isAttached()) { ComponentHelper.doDetach(child); assert !child.isAttached() : "Failure of " + getClass() + " to call super.onDetach()"; } setParent(null, child); } /** * Removes the item from the container. Fires the <i>BeforeRemove</i> event * before removing, then fires the <i>Remove</i> event after the widget has * been removed. * * @param item the item to remove * @return <code>true</code> if the item was removed */ protected boolean remove(T item) { return remove(item, false); } @SuppressWarnings("unchecked") protected boolean remove(T component, boolean force) { ContainerEvent containerEvent = createContainerEvent(component); containerEvent.setItem(component); containerEvent.setIndex(indexOf(component)); if (fireEvent(Events.BeforeRemove, containerEvent) || force) { ComponentEvent componentEvent = component.createComponentEvent(null); if (component.fireEvent(Events.BeforeOrphan, componentEvent) || force) { onRemove(component); if (attachChildren) { if (component.getParent() != this) { throw new RuntimeException("component is not a child of this container"); } orphan(component); } if (rendered) { Element elem = component.getElement(); Element parent = DOM.getParent(elem); if (parent != null) { DOM.removeChild(parent, elem); } } items.remove(component); component.fireEvent(Events.Orphan, componentEvent); fireEvent(Events.Remove, containerEvent); layoutNeeded = true; if (rendered && layoutOnChange) { layout(); } return true; } } return false; } protected boolean removeAll(boolean force) { int count = getItemCount(); for (int i = 0; i < count; i++) { remove(getItem(0), force); } return getItemCount() == 0; } /** * Sets the container's layout. * * @param layout the new layout */ protected void setLayout(Layout layout) { this.layout = layout; layoutNeeded = true; layout.setContainer(this); } protected void setLayoutNeeded(boolean layoutNeeded) { this.layoutNeeded = layoutNeeded; } protected void setLayoutOnChange(boolean change) { this.layoutOnChange = change; } /** * Helper Method for the subclasses that wish to support automatic wrapping of * Widget instances in WidgetComponents * * <p/> * If the widget is a component, no wrapping is performed * * @param widget the widget to be wrapped * @return the new component */ protected Component wrapWidget(Widget widget) { if (widget instanceof Component) { return (Component) widget; } else { return new WidgetComponent(widget); } } private native void setParent(Widget parent, Widget child) /*-{ child.@com.google.gwt.user.client.ui.Widget::parent = parent; }-*/; }