blob: 6fd392a3194a077c6e24c4561b2cbf708339cccc [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* 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 org.apache.cocoon.woody.formmodel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.apache.cocoon.woody.Constants;
import org.apache.cocoon.woody.FormContext;
import org.apache.cocoon.woody.event.WidgetEvent;
import org.apache.cocoon.xml.AttributesImpl;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
/**
* A repeater is a widget that repeats a number of other widgets.
*
* <p>Technically, the Repeater widget is a ContainerWidget whose children are
* {@link RepeaterRow}s, and the RepeaterRows in turn are ContainerWidgets
* containing the actual repeated widgets. However, in practice, you won't need
* to use the RepeaterRow widget directly.
*
* <p>Using the methods {@link #getSize()} and {@link #getWidget(int, java.lang.String)}
* you can access all of the repeated widget instances.
*
* @version $Id: Repeater.java,v 1.21 2004/03/05 13:02:32 bdelacretaz Exp $
*/
public class Repeater extends AbstractWidget implements ContainerWidget {
private RepeaterDefinition repeaterDefinition;
private List rows = new ArrayList();
public Repeater(RepeaterDefinition repeaterDefinition) {
this.repeaterDefinition = repeaterDefinition;
super.setDefinition(repeaterDefinition);
setLocation(definition.getLocation());
// setup initial size
removeRows();
}
public String getId() {
return definition.getId();
}
public int getSize() {
return rows.size();
}
public void addWidget(Widget widget) {
throw new RuntimeException("Repeater.addWidget(): Please use addRow() instead.");
}
public RepeaterRow addRow() {
RepeaterRow repeaterRow = new RepeaterRow(definition);
rows.add(repeaterRow);
return repeaterRow;
}
public RepeaterRow addRow(int index) {
RepeaterRow repeaterRow = new RepeaterRow(definition);
if (index >= this.rows.size()) {
rows.add(repeaterRow);
} else {
rows.add(index, repeaterRow);
}
return repeaterRow;
}
public RepeaterRow getRow(int index) {
return (RepeaterRow)rows.get(index);
}
/**
* Crawls up the parents of a widget up to finding a repeater row.
*
* @param widget the widget whose row is to be found
* @return the repeater row
*/
public static RepeaterRow getParentRow(Widget widget) {
Widget result = widget;
while(result != null && ! (result instanceof Repeater.RepeaterRow)) {
result = result.getParent();
}
if (result == null) {
throw new RuntimeException("Could not find a parent row for widget " + widget);
} else {
return (Repeater.RepeaterRow)result;
}
}
/**
* Get the position of a row in this repeater.
* @param row the row which we search the index for
* @return the row position or -1 if this row is not in this repeater
*/
public int indexOf(RepeaterRow row) {
return this.rows.indexOf(row);
}
/**
* @throws IndexOutOfBoundsException if the the index is outside the range of existing rows.
*/
public void removeRow(int index) {
rows.remove(index);
}
public void moveRowLeft(int index) {
if (index == 0 || index >= this.rows.size()) {
// do nothing
} else {
Object temp = this.rows.get(index-1);
this.rows.set(index-1, this.rows.get(index));
this.rows.set(index, temp);
}
}
public void moveRowRight(int index) {
if (index < 0 || index >= this.rows.size() - 1) {
// do nothing
} else {
Object temp = this.rows.get(index+1);
this.rows.set(index+1, this.rows.get(index));
this.rows.set(index, temp);
}
}
/**
* Clears all rows from the repeater and go back to the initial size
*/
public void removeRows() {
rows.clear();
// and reset to initial size
for (int i = 0; i < this.repeaterDefinition.getInitialSize(); i++) {
addRow();
}
}
/**
* Gets a widget on a certain row.
* @param rowIndex startin from 0
* @param id a widget id
* @return null if there's no such widget
*/
public Widget getWidget(int rowIndex, String id) {
RepeaterRow row = (RepeaterRow)rows.get(rowIndex);
return row.getWidget(id);
}
public boolean hasWidget(String id) {
int row;
try {
row = Integer.parseInt(id);
} catch (NumberFormatException e) {
// TODO: Use i18n.
throw new RuntimeException("Repeater: Row id is not a valid integer: " + id);
}
return row >= 0 && row < rows.size();
}
public Widget getWidget(String id) {
int row;
try {
row = Integer.parseInt(id);
} catch (NumberFormatException e) {
// TODO: Use i18n.
throw new RuntimeException("Repeater: Row id is not a valid integer: " + id);
}
return (RepeaterRow)rows.get(row);
}
public void readFromRequest(FormContext formContext) {
// read number of rows from request, and make an according number of rows
String sizeParameter = formContext.getRequest().getParameter(getFullyQualifiedId() + ".size");
if (sizeParameter != null) {
int size = 0;
try {
size = Integer.parseInt(sizeParameter);
} catch (NumberFormatException exc) {
// do nothing
}
// some protection against people who might try to exhaust the server by supplying very large
// size parameters
if (size > 500)
throw new RuntimeException("Client is not allowed to specify a repeater size larger than 500.");
int currentSize = getSize();
if (currentSize < size) {
for (int i = currentSize; i < size; i++) {
addRow();
}
} else if (currentSize > size) {
for (int i = currentSize - 1; i >= size; i--) {
removeRow(i);
}
}
}
// let the rows read their data from the request
Iterator rowIt = rows.iterator();
while (rowIt.hasNext()) {
RepeaterRow row = (RepeaterRow)rowIt.next();
row.readFromRequest(formContext);
}
}
public boolean validate(FormContext formContext) {
boolean valid = true;
Iterator rowIt = rows.iterator();
while (rowIt.hasNext()) {
RepeaterRow row = (RepeaterRow)rowIt.next();
valid = valid & row.validate(formContext);
}
return valid ? super.validate(formContext) : false;
}
private static final String REPEATER_EL = "repeater";
private static final String HEADINGS_EL = "headings";
private static final String HEADING_EL = "heading";
private static final String LABEL_EL = "label";
private static final String REPEATER_SIZE_EL = "repeater-size";
public void generateSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException {
AttributesImpl repeaterAttrs = new AttributesImpl();
repeaterAttrs.addCDATAAttribute("id", getFullyQualifiedId());
repeaterAttrs.addCDATAAttribute("size", String.valueOf(getSize()));
contentHandler.startElement(Constants.WI_NS, REPEATER_EL, Constants.WI_PREFIX_COLON + REPEATER_EL, repeaterAttrs);
// the repeater's label
contentHandler.startElement(Constants.WI_NS, LABEL_EL, Constants.WI_PREFIX_COLON + LABEL_EL, Constants.EMPTY_ATTRS);
definition.generateLabel(contentHandler);
contentHandler.endElement(Constants.WI_NS, LABEL_EL, Constants.WI_PREFIX_COLON + LABEL_EL);
// heading element -- currently contains the labels of each widget in the repeater
contentHandler.startElement(Constants.WI_NS, HEADINGS_EL, Constants.WI_PREFIX_COLON + HEADINGS_EL, Constants.EMPTY_ATTRS);
Iterator widgetDefinitionIt = repeaterDefinition.getWidgetDefinitions().iterator();
while (widgetDefinitionIt.hasNext()) {
WidgetDefinition widgetDefinition = (WidgetDefinition)widgetDefinitionIt.next();
contentHandler.startElement(Constants.WI_NS, HEADING_EL, Constants.WI_PREFIX_COLON + HEADING_EL, Constants.EMPTY_ATTRS);
widgetDefinition.generateLabel(contentHandler);
contentHandler.endElement(Constants.WI_NS, HEADING_EL, Constants.WI_PREFIX_COLON + HEADING_EL);
}
contentHandler.endElement(Constants.WI_NS, HEADINGS_EL, Constants.WI_PREFIX_COLON + HEADINGS_EL);
// the actual rows in the repeater
Iterator rowIt = rows.iterator();
while (rowIt.hasNext()) {
RepeaterRow row = (RepeaterRow)rowIt.next();
row.generateSaxFragment(contentHandler, locale);
}
contentHandler.endElement(Constants.WI_NS, REPEATER_EL, Constants.WI_PREFIX_COLON + REPEATER_EL);
}
public void generateLabel(ContentHandler contentHandler) throws SAXException {
definition.generateLabel(contentHandler);
}
/**
* Generates the label of a certain widget in this repeater.
*/
public void generateWidgetLabel(String widgetId, ContentHandler contentHandler) throws SAXException {
WidgetDefinition widgetDefinition = repeaterDefinition.getWidgetDefinition(widgetId);
if (widgetDefinition == null)
throw new SAXException("Repeater \"" + getFullyQualifiedId() + "\" contains no widget with id \"" + widgetId + "\".");
widgetDefinition.generateLabel(contentHandler);
}
/**
* Generates a repeater-size element with a size attribute indicating the size of this repeater.
*/
public void generateSize(ContentHandler contentHandler) throws SAXException {
AttributesImpl attrs = new AttributesImpl();
attrs.addCDATAAttribute("id", getFullyQualifiedId());
attrs.addCDATAAttribute("size", String.valueOf(getSize()));
contentHandler.startElement(Constants.WI_NS, REPEATER_SIZE_EL, Constants.WI_PREFIX_COLON + REPEATER_SIZE_EL, attrs);
contentHandler.endElement(Constants.WI_NS, REPEATER_SIZE_EL, Constants.WI_PREFIX_COLON + REPEATER_SIZE_EL);
}
public class RepeaterRow extends AbstractContainerWidget {
public RepeaterRow(AbstractWidgetDefinition definition) {
super(definition);
((ContainerDefinition)definition).createWidgets(this);
}
public String getLocation() {
return Repeater.this.getLocation();
}
public String getId() {
// id of a RepeaterRow is the position of the row in the list of rows.
return String.valueOf(rows.indexOf(this));
}
public Widget getParent() {
return Repeater.this;
}
public Form getForm() {
return Repeater.this.getForm();
}
public String getNamespace() {
return getParent().getNamespace() + "." + getId();
}
public String getFullyQualifiedId() {
return getParent().getNamespace() + "." + getId();
}
public void setParent(Widget widget) {
throw new RuntimeException("Parent of RepeaterRow is fixed, and cannot be set.");
}
public boolean validate(FormContext formContext) {
// Validate only child widtgets, as the definition's validators are those of the parent repeater
return widgets.validate(formContext);
}
private static final String ROW_EL = "repeater-row";
public void generateLabel(ContentHandler contentHandler) throws SAXException {
// this widget has no label
}
public void generateSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException {
AttributesImpl rowAttrs = new AttributesImpl();
rowAttrs.addCDATAAttribute("id", getFullyQualifiedId());
contentHandler.startElement(Constants.WI_NS, ROW_EL, Constants.WI_PREFIX_COLON + ROW_EL, rowAttrs);
Iterator widgetIt = widgets.iterator();
while (widgetIt.hasNext()) {
Widget widget = (Widget)widgetIt.next();
widget.generateSaxFragment(contentHandler, locale);
}
contentHandler.endElement(Constants.WI_NS, ROW_EL, Constants.WI_PREFIX_COLON + ROW_EL);
}
public void broadcastEvent(WidgetEvent event) {
throw new UnsupportedOperationException("Widget " + this.getFullyQualifiedId() + " doesn't handle events.");
}
}
/* (non-Javadoc)
* @see org.apache.cocoon.woody.formmodel.ContainerWidget#getChildren()
*/
public Iterator getChildren() {
// TODO Auto-generated method stub to make this compile again
return null;
}
}