001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.woody.formmodel;
018:
019: import java.util.ArrayList;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Locale;
023:
024: import org.apache.cocoon.woody.Constants;
025: import org.apache.cocoon.woody.FormContext;
026: import org.apache.cocoon.woody.event.WidgetEvent;
027: import org.apache.cocoon.xml.AttributesImpl;
028: import org.xml.sax.ContentHandler;
029: import org.xml.sax.SAXException;
030:
031: /**
032: * A repeater is a widget that repeats a number of other widgets.
033: *
034: * <p>Technically, the Repeater widget is a ContainerWidget whose children are
035: * {@link RepeaterRow}s, and the RepeaterRows in turn are ContainerWidgets
036: * containing the actual repeated widgets. However, in practice, you won't need
037: * to use the RepeaterRow widget directly.
038: *
039: * <p>Using the methods {@link #getSize()} and {@link #getWidget(int, java.lang.String)}
040: * you can access all of the repeated widget instances.
041: *
042: * @version $Id: Repeater.java 433543 2006-08-22 06:22:54Z crossley $
043: */
044: public class Repeater extends AbstractWidget implements ContainerWidget {
045: private RepeaterDefinition repeaterDefinition;
046: private List rows = new ArrayList();
047:
048: public Repeater(RepeaterDefinition repeaterDefinition) {
049: this .repeaterDefinition = repeaterDefinition;
050: super .setDefinition(repeaterDefinition);
051: setLocation(definition.getLocation());
052: // setup initial size
053: removeRows();
054: }
055:
056: public String getId() {
057: return definition.getId();
058: }
059:
060: public int getSize() {
061: return rows.size();
062: }
063:
064: public void addWidget(Widget widget) {
065: throw new RuntimeException(
066: "Repeater.addWidget(): Please use addRow() instead.");
067: }
068:
069: public RepeaterRow addRow() {
070: RepeaterRow repeaterRow = new RepeaterRow(definition);
071: rows.add(repeaterRow);
072: return repeaterRow;
073: }
074:
075: public RepeaterRow addRow(int index) {
076: RepeaterRow repeaterRow = new RepeaterRow(definition);
077: if (index >= this .rows.size()) {
078: rows.add(repeaterRow);
079: } else {
080: rows.add(index, repeaterRow);
081: }
082: return repeaterRow;
083: }
084:
085: public RepeaterRow getRow(int index) {
086: return (RepeaterRow) rows.get(index);
087: }
088:
089: /**
090: * Crawls up the parents of a widget up to finding a repeater row.
091: *
092: * @param widget the widget whose row is to be found
093: * @return the repeater row
094: */
095: public static RepeaterRow getParentRow(Widget widget) {
096: Widget result = widget;
097: while (result != null
098: && !(result instanceof Repeater.RepeaterRow)) {
099: result = result.getParent();
100: }
101:
102: if (result == null) {
103: throw new RuntimeException(
104: "Could not find a parent row for widget " + widget);
105:
106: } else {
107: return (Repeater.RepeaterRow) result;
108: }
109: }
110:
111: /**
112: * Get the position of a row in this repeater.
113: * @param row the row which we search the index for
114: * @return the row position or -1 if this row is not in this repeater
115: */
116: public int indexOf(RepeaterRow row) {
117: return this .rows.indexOf(row);
118: }
119:
120: /**
121: * @throws IndexOutOfBoundsException if the the index is outside the range of existing rows.
122: */
123: public void removeRow(int index) {
124: rows.remove(index);
125: }
126:
127: public void moveRowLeft(int index) {
128: if (index == 0 || index >= this .rows.size()) {
129: // do nothing
130: } else {
131: Object temp = this .rows.get(index - 1);
132: this .rows.set(index - 1, this .rows.get(index));
133: this .rows.set(index, temp);
134: }
135: }
136:
137: public void moveRowRight(int index) {
138: if (index < 0 || index >= this .rows.size() - 1) {
139: // do nothing
140: } else {
141: Object temp = this .rows.get(index + 1);
142: this .rows.set(index + 1, this .rows.get(index));
143: this .rows.set(index, temp);
144: }
145: }
146:
147: /**
148: * Clears all rows from the repeater and go back to the initial size
149: */
150: public void removeRows() {
151: rows.clear();
152:
153: // and reset to initial size
154: for (int i = 0; i < this .repeaterDefinition.getInitialSize(); i++) {
155: addRow();
156: }
157: }
158:
159: /**
160: * Gets a widget on a certain row.
161: * @param rowIndex startin from 0
162: * @param id a widget id
163: * @return null if there's no such widget
164: */
165: public Widget getWidget(int rowIndex, String id) {
166: RepeaterRow row = (RepeaterRow) rows.get(rowIndex);
167: return row.getWidget(id);
168: }
169:
170: public boolean hasWidget(String id) {
171: int row;
172: try {
173: row = Integer.parseInt(id);
174: } catch (NumberFormatException e) {
175: // TODO: Use i18n.
176: throw new RuntimeException(
177: "Repeater: Row id is not a valid integer: " + id);
178: }
179: return row >= 0 && row < rows.size();
180: }
181:
182: public Widget getWidget(String id) {
183: int row;
184: try {
185: row = Integer.parseInt(id);
186: } catch (NumberFormatException e) {
187: // TODO: Use i18n.
188: throw new RuntimeException(
189: "Repeater: Row id is not a valid integer: " + id);
190: }
191: return (RepeaterRow) rows.get(row);
192: }
193:
194: public void readFromRequest(FormContext formContext) {
195: // read number of rows from request, and make an according number of rows
196: String sizeParameter = formContext.getRequest().getParameter(
197: getFullyQualifiedId() + ".size");
198: if (sizeParameter != null) {
199: int size = 0;
200: try {
201: size = Integer.parseInt(sizeParameter);
202: } catch (NumberFormatException exc) {
203: // do nothing
204: }
205:
206: // some protection against people who might try to exhaust the server by supplying very large
207: // size parameters
208: if (size > 500)
209: throw new RuntimeException(
210: "Client is not allowed to specify a repeater size larger than 500.");
211:
212: int currentSize = getSize();
213: if (currentSize < size) {
214: for (int i = currentSize; i < size; i++) {
215: addRow();
216: }
217: } else if (currentSize > size) {
218: for (int i = currentSize - 1; i >= size; i--) {
219: removeRow(i);
220: }
221: }
222: }
223:
224: // let the rows read their data from the request
225: Iterator rowIt = rows.iterator();
226: while (rowIt.hasNext()) {
227: RepeaterRow row = (RepeaterRow) rowIt.next();
228: row.readFromRequest(formContext);
229: }
230: }
231:
232: public boolean validate(FormContext formContext) {
233: boolean valid = true;
234: Iterator rowIt = rows.iterator();
235: while (rowIt.hasNext()) {
236: RepeaterRow row = (RepeaterRow) rowIt.next();
237: valid = valid & row.validate(formContext);
238: }
239: return valid ? super .validate(formContext) : false;
240: }
241:
242: private static final String REPEATER_EL = "repeater";
243: private static final String HEADINGS_EL = "headings";
244: private static final String HEADING_EL = "heading";
245: private static final String LABEL_EL = "label";
246: private static final String REPEATER_SIZE_EL = "repeater-size";
247:
248: public void generateSaxFragment(ContentHandler contentHandler,
249: Locale locale) throws SAXException {
250: AttributesImpl repeaterAttrs = new AttributesImpl();
251: repeaterAttrs.addCDATAAttribute("id", getFullyQualifiedId());
252: repeaterAttrs.addCDATAAttribute("size", String
253: .valueOf(getSize()));
254: contentHandler.startElement(Constants.WI_NS, REPEATER_EL,
255: Constants.WI_PREFIX_COLON + REPEATER_EL, repeaterAttrs);
256:
257: // the repeater's label
258: contentHandler.startElement(Constants.WI_NS, LABEL_EL,
259: Constants.WI_PREFIX_COLON + LABEL_EL,
260: Constants.EMPTY_ATTRS);
261: definition.generateLabel(contentHandler);
262: contentHandler.endElement(Constants.WI_NS, LABEL_EL,
263: Constants.WI_PREFIX_COLON + LABEL_EL);
264:
265: // heading element -- currently contains the labels of each widget in the repeater
266: contentHandler.startElement(Constants.WI_NS, HEADINGS_EL,
267: Constants.WI_PREFIX_COLON + HEADINGS_EL,
268: Constants.EMPTY_ATTRS);
269: Iterator widgetDefinitionIt = repeaterDefinition
270: .getWidgetDefinitions().iterator();
271: while (widgetDefinitionIt.hasNext()) {
272: WidgetDefinition widgetDefinition = (WidgetDefinition) widgetDefinitionIt
273: .next();
274: contentHandler.startElement(Constants.WI_NS, HEADING_EL,
275: Constants.WI_PREFIX_COLON + HEADING_EL,
276: Constants.EMPTY_ATTRS);
277: widgetDefinition.generateLabel(contentHandler);
278: contentHandler.endElement(Constants.WI_NS, HEADING_EL,
279: Constants.WI_PREFIX_COLON + HEADING_EL);
280: }
281: contentHandler.endElement(Constants.WI_NS, HEADINGS_EL,
282: Constants.WI_PREFIX_COLON + HEADINGS_EL);
283:
284: // the actual rows in the repeater
285: Iterator rowIt = rows.iterator();
286: while (rowIt.hasNext()) {
287: RepeaterRow row = (RepeaterRow) rowIt.next();
288: row.generateSaxFragment(contentHandler, locale);
289: }
290: contentHandler.endElement(Constants.WI_NS, REPEATER_EL,
291: Constants.WI_PREFIX_COLON + REPEATER_EL);
292: }
293:
294: public void generateLabel(ContentHandler contentHandler)
295: throws SAXException {
296: definition.generateLabel(contentHandler);
297: }
298:
299: /**
300: * Generates the label of a certain widget in this repeater.
301: */
302: public void generateWidgetLabel(String widgetId,
303: ContentHandler contentHandler) throws SAXException {
304: WidgetDefinition widgetDefinition = repeaterDefinition
305: .getWidgetDefinition(widgetId);
306: if (widgetDefinition == null)
307: throw new SAXException("Repeater \""
308: + getFullyQualifiedId()
309: + "\" contains no widget with id \"" + widgetId
310: + "\".");
311: widgetDefinition.generateLabel(contentHandler);
312: }
313:
314: /**
315: * Generates a repeater-size element with a size attribute indicating the size of this repeater.
316: */
317: public void generateSize(ContentHandler contentHandler)
318: throws SAXException {
319: AttributesImpl attrs = new AttributesImpl();
320: attrs.addCDATAAttribute("id", getFullyQualifiedId());
321: attrs.addCDATAAttribute("size", String.valueOf(getSize()));
322: contentHandler.startElement(Constants.WI_NS, REPEATER_SIZE_EL,
323: Constants.WI_PREFIX_COLON + REPEATER_SIZE_EL, attrs);
324: contentHandler.endElement(Constants.WI_NS, REPEATER_SIZE_EL,
325: Constants.WI_PREFIX_COLON + REPEATER_SIZE_EL);
326: }
327:
328: public class RepeaterRow extends AbstractContainerWidget {
329:
330: public RepeaterRow(AbstractWidgetDefinition definition) {
331: super (definition);
332: ((ContainerDefinition) definition).createWidgets(this );
333: }
334:
335: public String getLocation() {
336: return Repeater.this .getLocation();
337: }
338:
339: public String getId() {
340: // id of a RepeaterRow is the position of the row in the list of rows.
341: return String.valueOf(rows.indexOf(this ));
342: }
343:
344: public Widget getParent() {
345: return Repeater.this ;
346: }
347:
348: public Form getForm() {
349: return Repeater.this .getForm();
350: }
351:
352: public String getNamespace() {
353: return getParent().getNamespace() + "." + getId();
354: }
355:
356: public String getFullyQualifiedId() {
357: return getParent().getNamespace() + "." + getId();
358: }
359:
360: public void setParent(Widget widget) {
361: throw new RuntimeException(
362: "Parent of RepeaterRow is fixed, and cannot be set.");
363: }
364:
365: public boolean validate(FormContext formContext) {
366: // Validate only child widtgets, as the definition's validators are those of the parent repeater
367: return widgets.validate(formContext);
368: }
369:
370: private static final String ROW_EL = "repeater-row";
371:
372: public void generateLabel(ContentHandler contentHandler)
373: throws SAXException {
374: // this widget has no label
375: }
376:
377: public void generateSaxFragment(ContentHandler contentHandler,
378: Locale locale) throws SAXException {
379: AttributesImpl rowAttrs = new AttributesImpl();
380: rowAttrs.addCDATAAttribute("id", getFullyQualifiedId());
381: contentHandler.startElement(Constants.WI_NS, ROW_EL,
382: Constants.WI_PREFIX_COLON + ROW_EL, rowAttrs);
383: Iterator widgetIt = widgets.iterator();
384: while (widgetIt.hasNext()) {
385: Widget widget = (Widget) widgetIt.next();
386: widget.generateSaxFragment(contentHandler, locale);
387: }
388: contentHandler.endElement(Constants.WI_NS, ROW_EL,
389: Constants.WI_PREFIX_COLON + ROW_EL);
390: }
391:
392: public void broadcastEvent(WidgetEvent event) {
393: throw new UnsupportedOperationException("Widget "
394: + this .getFullyQualifiedId()
395: + " doesn't handle events.");
396: }
397: }
398:
399: /* (non-Javadoc)
400: * @see org.apache.cocoon.woody.formmodel.ContainerWidget#getChildren()
401: */
402: public Iterator getChildren() {
403: // TODO Auto-generated method stub to make this compile again
404: return null;
405: }
406:
407: }
|