/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.click.extras.control;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.click.Control;
import org.apache.click.control.Container;
import org.apache.click.control.Field;
import org.apache.click.util.ClickUtils;
import org.apache.click.util.ContainerUtils;
import org.apache.click.util.HtmlStringBuffer;
/**
* Provides an abstract convenience class that implements Container and extend Field.
* <p/>
* If you need to bind a request parameter to this field's value, please see
* {@link #bindRequestValue()}.
* <p/>
* Here is an example of a Border Control that can wrap a Button and render
* a <tt>div</tt> border around it.
*
* <pre class="prettyprint">
* public class ButtonBorder extends AbstractContainerField {
* public ButtonBorder(String name) {
* super(name);
* }
*
* public String getTag() {
* return "div";
* }
*
* public Control add(Button button) {
* return getContainer().add(button);
* }
* } </pre>
*/
public abstract class AbstractContainerField extends Field implements Container {
// Constants --------------------------------------------------------------
private static final long serialVersionUID = 1L;
// Instance Variables -----------------------------------------------------
/** The list of controls. */
protected List<Control> controls;
/** The map of controls keyed by field name. */
protected Map<String, Control> controlMap;
// Constructors -----------------------------------------------------------
/**
* Create an AbstractContainerField with no name defined.
*/
public AbstractContainerField() {
}
/**
* Create an AbstractContainerField with the given name.
*
* @param name the ContainerField name
*/
public AbstractContainerField(String name) {
super(name);
}
/**
* Construct an AbstractContainerField with the given name and label.
*
* @param name the name of the Field
* @param label the label of the Field
*/
public AbstractContainerField(String name, String label) {
super(name, label);
}
// Public methods ---------------------------------------------------------
/**
* @see org.apache.click.control.Container#add(org.apache.click.Control).
*
* <b>Please note</b>: if the container contains a control with the same name
* as the given control, that control will be
* {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
* by the given control. If a control has no name defined it cannot be replaced.
*
* @param control the control to add to the container and return
* @return the control that was added to the container
* @throws IllegalArgumentException if the control is null
*/
public Control add(Control control) {
return insert(control, getControls().size());
}
/**
* Add the control to the container at the specified index, and return the
* added instance.
* <p/>
* <b>Please note</b>: if the container contains a control with the same name
* as the given control, that control will be
* {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
* by the given control. If a control has no name defined it cannot be replaced.
*
* @see org.apache.click.control.Container#insert(org.apache.click.Control, int).
*
* @param control the control to add to the container and return
* @param index the index at which the control is to be inserted
* @return the control that was added to the container
*
* @throws IllegalArgumentException if the control is null or if the control
* and container is the same instance
*/
public Control insert(Control control, int index) {
// Check if panel already contains the control
String controlName = control.getName();
if (controlName != null) {
// Check if container already contains the control
Control currentControl = getControlMap().get(control.getName());
// If container already contains the control do a replace
if (currentControl != null) {
// Current control and new control are referencing the same object
// so we exit early
if (currentControl == control) {
return control;
}
// If the two controls are different objects, we remove the current
// control and add the given control
return replace(currentControl, control);
}
}
return ContainerUtils.insert(this, control, index, getControlMap());
}
/**
* @see org.apache.click.control.Container#remove(org.apache.click.Control)
*
* @param control the control to remove from the container
* @return true if the control was removed from the container
*/
public boolean remove(Control control) {
return ContainerUtils.remove(this, control, getControlMap());
}
/**
* Replace the control in the container at the specified index, and return
* the newly added control.
*
* @see org.apache.click.control.Container#replace(org.apache.click.Control, org.apache.click.Control)
*
* @param currentControl the control currently contained in the container
* @param newControl the control to replace the current control contained in
* the container
* @return the new control that replaced the current control
*
* @throws IllegalArgumentException if the currentControl or newControl is
* null
* @throws IllegalStateException if the currentControl is not contained in
* the container
*/
public Control replace(Control currentControl, Control newControl) {
int controlIndex = getControls().indexOf(currentControl);
return ContainerUtils.replace(this, currentControl, newControl,
controlIndex, getControlMap());
}
/**
* Return the internal container instance.
*
* @deprecated the internal container instance was removed,
* AbstractContainerField can be used without accessing the internal
* container
*
* @return the internal container instance
*/
public Container getContainer() {
return this;
}
/**
* @see org.apache.click.control.Container#getControls()
*
* @return the sequential list of controls held by the container
*/
public List<Control> getControls() {
if (controls == null) {
controls = new ArrayList<Control>();
}
return controls;
}
/**
* @see org.apache.click.control.Container#getControl(java.lang.String)
*
* @param controlName the name of the control to get from the container
* @return the named control from the container if found or null otherwise
*/
public Control getControl(String controlName) {
if (hasControls()) {
return (Control) getControlMap().get(controlName);
}
return null;
}
/**
* @see org.apache.click.control.Container#contains(org.apache.click.Control)
*
* @param control the control whose presence in this container is to be tested
* @return true if the container contains the specified control
*/
public boolean contains(Control control) {
return getControls().contains(control);
}
/**
* Returns true if this container has existing controls, false otherwise.
*
* @see org.apache.click.control.AbstractContainer#hasControls()
*
* @return true if the container has existing controls, false otherwise.
*/
public boolean hasControls() {
return (controls == null) ? false : !controls.isEmpty();
}
/**
* Set the parent of the Field.
*
* @see org.apache.click.Control#setParent(Object)
*
* @param parent the parent of the Control
* @throws IllegalArgumentException if the given parent instance is
* referencing <tt>this</tt> object: <tt>if (parent == this)</tt>
*/
@Override
public void setParent(Object parent) {
if (parent == this) {
throw new IllegalArgumentException("Cannot set parent to itself");
}
this.parent = parent;
}
/**
* This method does nothing by default.
* <p/>
* Subclasses can override this method to binds the submitted request
* value to the Field value. For example:
* <p/>
* <pre class="prettyprint">
* public CoolField extends AbstractContainerField {
*
* public CoolField(String name) {
* super(name);
* }
*
* public void bindRequestValue() {
* setValue(getRequestValue());
* }
* } </pre>
*
* @see org.apache.click.control.Field#getRequestValue()
*/
@Override
public void bindRequestValue() {
}
/**
* @see org.apache.click.Control#onProcess()
*
* @return true to continue Page event processing or false otherwise
*/
@Override
public boolean onProcess() {
boolean continueProcessing = super.onProcess();
if (hasControls()) {
for (Control control : getControls()) {
if (!control.onProcess()) {
continueProcessing = false;
}
}
}
return continueProcessing;
}
/**
* @see org.apache.click.Control#onDestroy()
*/
@Override
public void onDestroy() {
if (hasControls()) {
for (int i = 0, size = getControls().size(); i < size; i++) {
Control control = (Control) getControls().get(i);
try {
control.onDestroy();
} catch (Throwable t) {
ClickUtils.getLogService().error("onDestroy error", t);
}
}
}
}
/**
* @see org.apache.click.Control#onInit()
*/
@Override
public void onInit() {
super.onInit();
if (hasControls()) {
for (int i = 0, size = getControls().size(); i < size; i++) {
Control control = (Control) getControls().get(i);
control.onInit();
}
}
}
/**
* @see org.apache.click.Control#onRender()
*/
@Override
public void onRender() {
if (hasControls()) {
for (int i = 0, size = getControls().size(); i < size; i++) {
Control control = (Control) getControls().get(i);
control.onRender();
}
}
}
/**
* By default render the container and all its child controls to the
* specified buffer.
* <p/>
* If {@link org.apache.click.control.AbstractControl#getTag()} returns null,
* this method will render only its child controls.
* <p/>
* @see org.apache.click.control.AbstractControl#render(org.apache.click.util.HtmlStringBuffer)
*
* @param buffer the specified buffer to render the control's output to
*/
@Override
public void render(HtmlStringBuffer buffer) {
//If tag is set, render it
if (getTag() != null) {
renderTagBegin(getTag(), buffer);
buffer.closeTag();
if (hasControls()) {
buffer.append("\n");
}
renderContent(buffer);
renderTagEnd(getTag(), buffer);
buffer.append("\n");
} else {
//render only content because no tag is specified
if (hasControls()) {
renderContent(buffer);
}
}
}
/**
* Returns the HTML representation of this control.
* <p/>
* This method delegates the rendering to the method
* {@link #render(org.apache.click.util.HtmlStringBuffer)}. The size of buffer
* is determined by {@link #getControlSizeEst()}.
*
* @return the HTML representation of this control
*/
@Override
public String toString() {
HtmlStringBuffer buffer = new HtmlStringBuffer(getControlSizeEst());
render(buffer);
return buffer.toString();
}
// Protected Methods ------------------------------------------------------
/**
* @see org.apache.click.control.AbstractControl#renderTagEnd(java.lang.String, org.apache.click.util.HtmlStringBuffer)
*
* @param tagName the name of the tag to close
* @param buffer the buffer to append the output to
*/
@Override
protected void renderTagEnd(String tagName, HtmlStringBuffer buffer) {
buffer.elementEnd(tagName);
}
/**
* Render this container content to the specified buffer.
*
* @see org.apache.click.control.AbstractContainer#renderContent(org.apache.click.util.HtmlStringBuffer)
*
* @param buffer the buffer to append the output to
*/
protected void renderContent(HtmlStringBuffer buffer) {
renderChildren(buffer);
}
/**
* Render this container children to the specified buffer.
*
* @see org.apache.click.control.AbstractContainer#renderChildren(org.apache.click.util.HtmlStringBuffer)
*
* @param buffer the buffer to append the output to
*/
protected void renderChildren(HtmlStringBuffer buffer) {
if (hasControls()) {
for (int i = 0; i < getControls().size(); i++) {
Control control = (Control) getControls().get(i);
int before = buffer.length();
control.render(buffer);
int after = buffer.length();
if (before != after) {
buffer.append("\n");
}
}
}
}
/**
* Return the map of controls where each map's key / value pair will consist
* of the control name and instance.
*
* @see org.apache.click.control.AbstractContainer#getControlMap()
*
* @return the map of controls
*/
protected Map<String, Control> getControlMap() {
if (controlMap == null) {
controlMap = new HashMap<String, Control>();
}
return controlMap;
}
/**
* @see org.apache.click.control.AbstractControl#getControlSizeEst()
*
* @return the estimated rendered control size in characters
*/
@Override
protected int getControlSizeEst() {
int size = 20;
if (getTag() != null && hasAttributes()) {
size += 20 * getAttributes().size();
}
if (hasControls()) {
size += getControls().size() * size;
}
return size;
}
}