KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cocoon > forms > formmodel > Repeater


1 /*
2  * Copyright 1999-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.cocoon.forms.formmodel;
17
18 import java.util.ArrayList JavaDoc;
19 import java.util.Iterator JavaDoc;
20 import java.util.List JavaDoc;
21 import java.util.Locale JavaDoc;
22
23 import org.apache.cocoon.forms.FormsConstants;
24 import org.apache.cocoon.forms.FormContext;
25 import org.apache.cocoon.forms.util.I18nMessage;
26 import org.apache.cocoon.forms.event.WidgetEvent;
27 import org.apache.cocoon.forms.validation.ValidationError;
28 import org.apache.cocoon.forms.validation.ValidationErrorAware;
29 import org.apache.cocoon.xml.AttributesImpl;
30 import org.apache.cocoon.xml.XMLUtils;
31 import org.xml.sax.ContentHandler JavaDoc;
32 import org.xml.sax.SAXException JavaDoc;
33
34 /**
35  * A repeater is a widget that repeats a number of other widgets.
36  *
37  * <p>Technically, the Repeater widget is a ContainerWidget whose children are
38  * {@link RepeaterRow}s, and the RepeaterRows in turn are ContainerWidgets
39  * containing the actual repeated widgets. However, in practice, you won't need
40  * to use the RepeaterRow widget directly.
41  *
42  * <p>Using the methods {@link #getSize()} and {@link #getWidget(int, java.lang.String)}
43  * you can access all of the repeated widget instances.
44  *
45  * @version $Id: Repeater.java 326838 2005-10-20 06:26:53Z sylvain $
46  */

47 public class Repeater extends AbstractWidget
48                       implements ValidationErrorAware {
49
50     private static final String JavaDoc REPEATER_EL = "repeater";
51     private static final String JavaDoc HEADINGS_EL = "headings";
52     private static final String JavaDoc HEADING_EL = "heading";
53     private static final String JavaDoc LABEL_EL = "label";
54     private static final String JavaDoc REPEATER_SIZE_EL = "repeater-size";
55
56     private final RepeaterDefinition definition;
57     private final List JavaDoc rows = new ArrayList JavaDoc();
58     protected ValidationError validationError;
59
60
61     public Repeater(RepeaterDefinition repeaterDefinition) {
62         super(repeaterDefinition);
63         this.definition = repeaterDefinition;
64         // Setup initial size. Do not call addRow() as it will call initialize()
65
// on the newly created rows, which is not what we want here.
66
for (int i = 0; i < this.definition.getInitialSize(); i++) {
67             rows.add(new RepeaterRow(definition));
68         }
69     }
70
71     public WidgetDefinition getDefinition() {
72         return definition;
73     }
74
75     public void initialize() {
76         for (int i = 0; i < this.rows.size(); i++) {
77             ((RepeaterRow)rows.get(i)).initialize();
78         }
79         super.initialize();
80     }
81
82     public int getSize() {
83         return rows.size();
84     }
85
86     public int getMinSize() {
87         return this.definition.getMinSize();
88     }
89
90     public int getMaxSize() {
91         return this.definition.getMaxSize();
92     }
93
94     public RepeaterRow addRow() {
95         RepeaterRow repeaterRow = new RepeaterRow(definition);
96         rows.add(repeaterRow);
97         repeaterRow.initialize();
98         getForm().addWidgetUpdate(this);
99         return repeaterRow;
100     }
101
102     public RepeaterRow addRow(int index) {
103         RepeaterRow repeaterRow = new RepeaterRow(definition);
104         if (index >= this.rows.size()) {
105             rows.add(repeaterRow);
106         } else {
107             rows.add(index, repeaterRow);
108         }
109         repeaterRow.initialize();
110         getForm().addWidgetUpdate(this);
111         return repeaterRow;
112     }
113
114     public RepeaterRow getRow(int index) {
115         return (RepeaterRow)rows.get(index);
116     }
117
118     /**
119      * Overrides {@link AbstractWidget#getChild(String)} to return the
120      * repeater-row indicated by the index in 'id'
121      *
122      * @param id index of the row as a string-id
123      * @return the repeater-row at the specified index
124      */

125     public Widget getChild(String JavaDoc id) {
126         int rowIndex;
127         try {
128             rowIndex = Integer.parseInt(id);
129         } catch (NumberFormatException JavaDoc nfe) {
130             // Not a number
131
return null;
132         }
133
134         if (rowIndex < 0 || rowIndex >= getSize()) {
135             return null;
136         }
137
138         return getRow(rowIndex);
139     }
140
141     /**
142      * Crawls up the parents of a widget up to finding a repeater row.
143      *
144      * @param widget the widget whose row is to be found
145      * @return the repeater row
146      */

147     public static RepeaterRow getParentRow(Widget widget) {
148         Widget result = widget;
149         while(result != null && ! (result instanceof Repeater.RepeaterRow)) {
150             result = result.getParent();
151         }
152
153         if (result == null) {
154             throw new RuntimeException JavaDoc("Could not find a parent row for widget " + widget);
155
156         }
157         return (Repeater.RepeaterRow)result;
158     }
159
160     /**
161      * Get the position of a row in this repeater.
162      * @param row the row which we search the index for
163      * @return the row position or -1 if this row is not in this repeater
164      */

165     public int indexOf(RepeaterRow row) {
166         return this.rows.indexOf(row);
167     }
168
169     /**
170      * @throws IndexOutOfBoundsException if the the index is outside the range of existing rows.
171      */

172     public void removeRow(int index) {
173         rows.remove(index);
174         getForm().addWidgetUpdate(this);
175     }
176
177     /**
178      * Move a row from one place to another
179      * @param from the existing row position
180      * @param to the target position. The "from" item will be moved before that position.
181      */

182     public void moveRow(int from, int to) {
183         int size = this.rows.size();
184         
185         if (from < 0 || from >= size || to < 0 || to > size) {
186             throw new IllegalArgumentException JavaDoc("Cannot move from " + from + " to " + to +
187                     " on repeater with " + size + " rows");
188         }
189         
190         if (from == to) {
191             return;
192         }
193         
194         Object JavaDoc fromRow = this.rows.remove(from);
195         if (to == size) {
196             // Move at the end
197
this.rows.add(fromRow);
198             
199         } else if (to > from) {
200             // Index of "to" was moved by removing
201
this.rows.add(to - 1, fromRow);
202             
203         } else {
204             this.rows.add(to, fromRow);
205         }
206
207         getForm().addWidgetUpdate(this);
208     }
209
210     public void moveRowLeft(int index) {
211         if (index == 0 || index >= this.rows.size()) {
212             // do nothing
213
} else {
214             Object JavaDoc temp = this.rows.get(index-1);
215             this.rows.set(index-1, this.rows.get(index));
216             this.rows.set(index, temp);
217         }
218         getForm().addWidgetUpdate(this);
219     }
220
221     public void moveRowRight(int index) {
222         if (index < 0 || index >= this.rows.size() - 1) {
223             // do nothing
224
} else {
225             Object JavaDoc temp = this.rows.get(index+1);
226             this.rows.set(index+1, this.rows.get(index));
227             this.rows.set(index, temp);
228         }
229         getForm().addWidgetUpdate(this);
230     }
231
232     /**
233      * @deprecated {@see #clear()}
234      *
235      */

236     public void removeRows() {
237         clear();
238         getForm().addWidgetUpdate(this);
239     }
240
241     /**
242      * Clears all rows from the repeater and go back to the initial size
243      */

244     public void clear() {
245         rows.clear();
246
247         // and reset to initial size
248
for (int i = 0; i < this.definition.getInitialSize(); i++) {
249             addRow();
250         }
251         getForm().addWidgetUpdate(this);
252     }
253
254     /**
255      * Gets a widget on a certain row.
256      * @param rowIndex startin from 0
257      * @param id a widget id
258      * @return null if there's no such widget
259      */

260     public Widget getWidget(int rowIndex, String JavaDoc id) {
261         RepeaterRow row = (RepeaterRow)rows.get(rowIndex);
262         return row.getChild(id);
263     }
264
265     public void readFromRequest(FormContext formContext) {
266         if (!getCombinedState().isAcceptingInputs())
267             return;
268
269         // read number of rows from request, and make an according number of rows
270
String JavaDoc sizeParameter = formContext.getRequest().getParameter(getRequestParameterName() + ".size");
271         if (sizeParameter != null) {
272             int size = 0;
273             try {
274                 size = Integer.parseInt(sizeParameter);
275             } catch (NumberFormatException JavaDoc exc) {
276                 // do nothing
277
}
278
279             // some protection against people who might try to exhaust the server by supplying very large
280
// size parameters
281
if (size > 500)
282                 throw new RuntimeException JavaDoc("Client is not allowed to specify a repeater size larger than 500.");
283
284             int currentSize = getSize();
285             if (currentSize < size) {
286                 for (int i = currentSize; i < size; i++) {
287                     addRow();
288                 }
289             } else if (currentSize > size) {
290                 for (int i = currentSize - 1; i >= size; i--) {
291                     removeRow(i);
292                 }
293             }
294         }
295
296         // let the rows read their data from the request
297
Iterator JavaDoc rowIt = rows.iterator();
298         while (rowIt.hasNext()) {
299             RepeaterRow row = (RepeaterRow)rowIt.next();
300             row.readFromRequest(formContext);
301         }
302     }
303
304     /**
305      * @see org.apache.cocoon.forms.formmodel.Widget#validate()
306      */

307     public boolean validate() {
308         if (!getCombinedState().isValidatingValues()) {
309             this.wasValid = true;
310             return true;
311         }
312
313         boolean valid = true;
314         Iterator JavaDoc rowIt = rows.iterator();
315         while (rowIt.hasNext()) {
316             RepeaterRow row = (RepeaterRow)rowIt.next();
317             valid = valid & row.validate();
318         }
319
320         if (rows.size() > getMaxSize() || rows.size() < getMinSize()) {
321             String JavaDoc [] boundaries = new String JavaDoc[2];
322             boundaries[0] = String.valueOf(getMinSize());
323             boundaries[1] = String.valueOf(getMaxSize());
324             this.validationError = new ValidationError(new I18nMessage("repeater.cardinality", boundaries, FormsConstants.I18N_CATALOGUE));
325             valid=false;
326         }
327
328
329         if (valid) {
330             valid = super.validate();
331         }
332
333         this.wasValid = valid && this.validationError == null;
334         return this.wasValid;
335     }
336
337
338
339     /**
340      * @return "repeater"
341      */

342     public String JavaDoc getXMLElementName() {
343         return REPEATER_EL;
344     }
345
346
347
348     /**
349      * Adds @size attribute
350      */

351     public AttributesImpl getXMLElementAttributes() {
352         AttributesImpl attrs = super.getXMLElementAttributes();
353         attrs.addCDATAAttribute("size", String.valueOf(getSize()));
354         // Generate the min and max sizes if they don't have the default value
355
int size = getMinSize();
356         if (size > 0) {
357             attrs.addCDATAAttribute("min-size", String.valueOf(size));
358         }
359         size = getMaxSize();
360         if (size != Integer.MAX_VALUE) {
361             attrs.addCDATAAttribute("max-size", String.valueOf(size));
362         }
363         return attrs;
364     }
365
366
367     public void generateDisplayData(ContentHandler JavaDoc contentHandler)
368             throws SAXException JavaDoc {
369         // the repeater's label
370
contentHandler.startElement(FormsConstants.INSTANCE_NS, LABEL_EL, FormsConstants.INSTANCE_PREFIX_COLON + LABEL_EL, XMLUtils.EMPTY_ATTRIBUTES);
371         generateLabel(contentHandler);
372         contentHandler.endElement(FormsConstants.INSTANCE_NS, LABEL_EL, FormsConstants.INSTANCE_PREFIX_COLON + LABEL_EL);
373
374         // heading element -- currently contains the labels of each widget in the repeater
375
contentHandler.startElement(FormsConstants.INSTANCE_NS, HEADINGS_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADINGS_EL, XMLUtils.EMPTY_ATTRIBUTES);
376         Iterator JavaDoc widgetDefinitionIt = definition.getWidgetDefinitions().iterator();
377         while (widgetDefinitionIt.hasNext()) {
378             WidgetDefinition widgetDefinition = (WidgetDefinition)widgetDefinitionIt.next();
379             contentHandler.startElement(FormsConstants.INSTANCE_NS, HEADING_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADING_EL, XMLUtils.EMPTY_ATTRIBUTES);
380             widgetDefinition.generateLabel(contentHandler);
381             contentHandler.endElement(FormsConstants.INSTANCE_NS, HEADING_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADING_EL);
382         }
383         contentHandler.endElement(FormsConstants.INSTANCE_NS, HEADINGS_EL, FormsConstants.INSTANCE_PREFIX_COLON + HEADINGS_EL);
384     }
385
386
387     public void generateItemSaxFragment(ContentHandler JavaDoc contentHandler, Locale JavaDoc locale) throws SAXException JavaDoc {
388         // the actual rows in the repeater
389
Iterator JavaDoc rowIt = rows.iterator();
390         while (rowIt.hasNext()) {
391             RepeaterRow row = (RepeaterRow)rowIt.next();
392             row.generateSaxFragment(contentHandler, locale);
393         }
394     }
395
396     /**
397      * Generates the label of a certain widget in this repeater.
398      */

399     public void generateWidgetLabel(String JavaDoc widgetId, ContentHandler JavaDoc contentHandler) throws SAXException JavaDoc {
400         WidgetDefinition widgetDefinition = definition.getWidgetDefinition(widgetId);
401         if (widgetDefinition == null)
402             throw new SAXException JavaDoc("Repeater \"" + getRequestParameterName() + "\" at " + this.getLocation()
403                                    + " contains no widget with id \"" + widgetId + "\".");
404         widgetDefinition.generateLabel(contentHandler);
405     }
406
407     /**
408      * Generates a repeater-size element with a size attribute indicating the size of this repeater.
409      */

410     public void generateSize(ContentHandler JavaDoc contentHandler) throws SAXException JavaDoc {
411         AttributesImpl attrs = getXMLElementAttributes();
412         contentHandler.startElement(FormsConstants.INSTANCE_NS, REPEATER_SIZE_EL, FormsConstants.INSTANCE_PREFIX_COLON + REPEATER_SIZE_EL, attrs);
413         contentHandler.endElement(FormsConstants.INSTANCE_NS, REPEATER_SIZE_EL, FormsConstants.INSTANCE_PREFIX_COLON + REPEATER_SIZE_EL);
414     }
415
416     /**
417      * Set a validation error on this field. This allows repeaters be externally marked as invalid by
418      * application logic.
419      *
420      * @return the validation error
421      */

422     public ValidationError getValidationError() {
423         return this.validationError;
424     }
425
426     /**
427      * set a validation error
428      */

429     public void setValidationError(ValidationError error) {
430         this.validationError = error;
431     }
432
433     public class RepeaterRow extends AbstractContainerWidget {
434
435         private static final String JavaDoc ROW_EL = "repeater-row";
436
437         public RepeaterRow(RepeaterDefinition definition) {
438             super(definition);
439             setParent(Repeater.this);
440             definition.createWidgets(this);
441         }
442
443         public WidgetDefinition getDefinition() {
444             return Repeater.this.definition;
445         }
446
447         private int cachedPosition = -100;
448         private String JavaDoc cachedId = "--undefined--";
449
450         public String JavaDoc getId() {
451             int pos = rows.indexOf(this);
452             if (pos == -1) {
453                 throw new IllegalStateException JavaDoc("Row has currently no position");
454             }
455             if (pos != this.cachedPosition) {
456                 this.cachedPosition = pos;
457                 // id of a RepeaterRow is the position of the row in the list of rows.
458
this.cachedId = String.valueOf(pos);
459                 widgetNameChanged();
460             }
461             return this.cachedId;
462         }
463         
464         public String JavaDoc getRequestParameterName() {
465             // Get the id to check potential position change
466
getId();
467
468             return super.getRequestParameterName();
469         }
470
471         public Form getForm() {
472             return Repeater.this.getForm();
473         }
474
475         public void initialize() {
476             // Initialize children but don't call super.initialize() that would call the repeater's
477
// on-create handlers for each row.
478
// FIXME(SW): add an 'on-create-row' handler?
479
Iterator JavaDoc it = this.getChildren();
480             while(it.hasNext()) {
481               ((Widget)it.next()).initialize();
482             }
483         }
484
485         public boolean validate() {
486             // Validate only child widtgets, as the definition's validators are those of the parent repeater
487
return widgets.validate();
488         }
489
490         /**
491          * @return "repeater-row"
492          */

493         public String JavaDoc getXMLElementName() {
494             return ROW_EL;
495         }
496
497         public void generateLabel(ContentHandler JavaDoc contentHandler) throws SAXException JavaDoc {
498             // this widget has its label generated in the context of the repeater
499
}
500
501         public void generateDisplayData(ContentHandler JavaDoc contentHandler)
502                 throws SAXException JavaDoc {
503             // this widget has its display-data generated in the context of the repeater
504
}
505
506         public void broadcastEvent(WidgetEvent event) {
507             throw new UnsupportedOperationException JavaDoc("Widget " + this.getRequestParameterName() + " doesn't handle events.");
508         }
509     }
510
511 }
512
Popular Tags