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.forms.formmodel;
018:
019: import java.lang.reflect.Array;
020: import java.util.Locale;
021:
022: import org.apache.cocoon.forms.FormsConstants;
023: import org.apache.cocoon.forms.FormContext;
024: import org.apache.cocoon.forms.datatype.Datatype;
025: import org.apache.cocoon.forms.datatype.SelectionList;
026: import org.apache.cocoon.forms.datatype.convertor.ConversionResult;
027: import org.apache.cocoon.forms.datatype.convertor.Convertor;
028: import org.apache.cocoon.forms.event.*;
029: import org.apache.cocoon.forms.util.I18nMessage;
030: import org.apache.cocoon.forms.validation.ValidationError;
031: import org.apache.cocoon.forms.validation.ValidationErrorAware;
032: import org.apache.cocoon.xml.XMLUtils;
033: import org.xml.sax.ContentHandler;
034: import org.xml.sax.SAXException;
035:
036: /**
037: * A MultiValueField is mostly the same as a normal {@link Field}, but can
038: * hold multiple values. A MultiValueField should have a Datatype which
039: * has a SelectionList, because the user will always select the values
040: * from a list. A MultiValueField has no concept of "required", you should
041: * instead use the ValueCountValidationRule to check how many items the user
042: * has selected.
043: *
044: * <p>A MultiValueField also has a {@link Datatype} associated with it. In
045: * case of MultiValueFields, this Datatype will always be an array
046: * type, thus {@link Datatype#isArrayType()} will always return true, and
047: * this in return has an influence on the kind of validation rules that
048: * can be used with the Datatype (see {@link Datatype} description for more
049: * information).</p>
050: *
051: * @version $Id: MultiValueField.java 462520 2006-10-10 19:39:14Z vgritsenko $
052: */
053: public class MultiValueField extends AbstractWidget implements
054: ValidationErrorAware, SelectableWidget, DataWidget,
055: ValueChangedListenerEnabled {
056:
057: private static final String MULTIVALUEFIELD_EL = "multivaluefield";
058: private static final String VALUES_EL = "values";
059: private static final String VALUE_EL = "value";
060: private static final String VALIDATION_MSG_EL = "validation-message";
061:
062: private final MultiValueFieldDefinition definition;
063:
064: private SelectionList selectionList;
065: private String[] enteredValues;
066: private String invalidEnteredValue;
067: private Object[] values;
068: private ValidationError validationError;
069: private ValueChangedListener listener;
070:
071: public MultiValueField(MultiValueFieldDefinition definition) {
072: super (definition);
073: this .definition = definition;
074: this .listener = definition.getValueChangedListener();
075: }
076:
077: public void initialize() {
078: this .selectionList = this .definition.getSelectionList();
079: super .initialize();
080: }
081:
082: public WidgetDefinition getDefinition() {
083: return definition;
084: }
085:
086: public void readFromRequest(FormContext formContext) {
087: if (!getCombinedState().isAcceptingInputs()) {
088: return;
089: }
090:
091: enteredValues = formContext.getRequest().getParameterValues(
092: getRequestParameterName());
093: invalidEnteredValue = null;
094: validationError = null;
095: Object[] oldValues = values;
096: values = null;
097:
098: boolean conversionFailed = false;
099: if (enteredValues != null) {
100: Object[] tempValues = (Object[]) Array.newInstance(
101: getDatatype().getTypeClass(), enteredValues.length);
102: for (int i = 0; i < enteredValues.length; i++) {
103: String param = enteredValues[i];
104: ConversionResult conversionResult = definition
105: .getDatatype().convertFromString(param,
106: formContext.getLocale());
107: if (conversionResult.isSuccessful()) {
108: tempValues[i] = conversionResult.getResult();
109: } else {
110: conversionFailed = true;
111: invalidEnteredValue = param;
112: break;
113: }
114: }
115:
116: if (!conversionFailed)
117: values = tempValues;
118: else
119: values = null;
120: } else {
121: values = new Object[0];
122: }
123:
124: engenderChangeEvent(oldValues);
125: }
126:
127: private void engenderChangeEvent(Object[] oldValues) {
128: boolean hasListeners = hasValueChangedListeners()
129: || this .getForm().hasFormHandler();
130: if (hasListeners) {
131: if (values != null) {
132: boolean changed = false;
133: if (oldValues == null) {
134: changed = true;
135: } else if (oldValues.length != values.length) {
136: changed = true;
137: } else {
138: for (int i = 0; i < values.length; i++) {
139: if (!values[i].equals(oldValues[i])) {
140: changed = true;
141: break;
142: }
143: }
144: }
145: if (changed)
146: getForm().addWidgetEvent(
147: new ValueChangedEvent(this , oldValues,
148: values));
149: }
150: }
151: }
152:
153: public boolean validate() {
154: if (!getCombinedState().isValidatingValues()) {
155: this .wasValid = true;
156: return true;
157: }
158:
159: if (values != null) {
160: validationError = definition.getDatatype().validate(values,
161: new ExpressionContextImpl(this ));
162: } else if (invalidEnteredValue != null) {
163: validationError = new ValidationError(new I18nMessage(
164: "multivaluefield.conversionfailed",
165: new String[] { invalidEnteredValue },
166: FormsConstants.I18N_CATALOGUE));
167: }
168:
169: this .wasValid = validationError == null ? super .validate()
170: : false;
171: return this .wasValid;
172: }
173:
174: /**
175: * @return "multivaluefield"
176: */
177: public String getXMLElementName() {
178: return MULTIVALUEFIELD_EL;
179: }
180:
181: public void generateItemSaxFragment(ContentHandler contentHandler,
182: Locale locale) throws SAXException {
183: contentHandler.startElement(FormsConstants.INSTANCE_NS,
184: VALUES_EL, FormsConstants.INSTANCE_PREFIX_COLON
185: + VALUES_EL, XMLUtils.EMPTY_ATTRIBUTES);
186: Convertor convertor = definition.getDatatype().getConvertor();
187: if (values != null) {
188: for (int i = 0; i < values.length; i++) {
189: contentHandler.startElement(FormsConstants.INSTANCE_NS,
190: VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON
191: + VALUE_EL, XMLUtils.EMPTY_ATTRIBUTES);
192: String value = convertor.convertToString(values[i],
193: locale, null);
194: contentHandler.characters(value.toCharArray(), 0, value
195: .length());
196: contentHandler.endElement(FormsConstants.INSTANCE_NS,
197: VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON
198: + VALUE_EL);
199: }
200: } else if (enteredValues != null) {
201: for (int i = 0; i < enteredValues.length; i++) {
202: contentHandler.startElement(FormsConstants.INSTANCE_NS,
203: VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON
204: + VALUE_EL, XMLUtils.EMPTY_ATTRIBUTES);
205: String value = enteredValues[i];
206: contentHandler.characters(value.toCharArray(), 0, value
207: .length());
208: contentHandler.endElement(FormsConstants.INSTANCE_NS,
209: VALUE_EL, FormsConstants.INSTANCE_PREFIX_COLON
210: + VALUE_EL);
211: }
212: }
213: contentHandler.endElement(FormsConstants.INSTANCE_NS,
214: VALUES_EL, FormsConstants.INSTANCE_PREFIX_COLON
215: + VALUES_EL);
216:
217: // validation message element
218: if (validationError != null) {
219: contentHandler.startElement(FormsConstants.INSTANCE_NS,
220: VALIDATION_MSG_EL,
221: FormsConstants.INSTANCE_PREFIX_COLON
222: + VALIDATION_MSG_EL,
223: XMLUtils.EMPTY_ATTRIBUTES);
224: validationError.generateSaxFragment(contentHandler);
225: contentHandler.endElement(FormsConstants.INSTANCE_NS,
226: VALIDATION_MSG_EL,
227: FormsConstants.INSTANCE_PREFIX_COLON
228: + VALIDATION_MSG_EL);
229: }
230:
231: // the selection list
232: if (this .selectionList != null) {
233: this .selectionList.generateSaxFragment(contentHandler,
234: locale);
235: }
236:
237: // include some info about the datatype
238: definition.getDatatype().generateSaxFragment(contentHandler,
239: locale);
240: }
241:
242: public Object getValue() {
243: return values;
244: }
245:
246: public void setValue(Object value) {
247: if (value == null) {
248: setValues(new Object[0]);
249: } else if (value.getClass().isArray()) {
250: setValues((Object[]) value);
251: } else {
252: throw new RuntimeException("Cannot set value of field '"
253: + getRequestParameterName()
254: + "' with an object of type "
255: + value.getClass().getName());
256: }
257: getForm().addWidgetUpdate(this );
258: }
259:
260: public void setValues(Object[] values) {
261: // check that all the objects in the array correspond to the datatype
262: for (int i = 0; i < values.length; i++) {
263: if (!definition.getDatatype().getTypeClass()
264: .isAssignableFrom(values[i].getClass())) {
265: throw new RuntimeException(
266: "Cannot set value of field '"
267: + getRequestParameterName()
268: + "' with an object of type "
269: + values[i].getClass().getName());
270: }
271: }
272: Object[] oldValues = this .values;
273: this .values = values;
274: engenderChangeEvent(oldValues);
275: getForm().addWidgetUpdate(this );
276: }
277:
278: /**
279: * Set this field's selection list.
280: * @param selectionList The new selection list.
281: */
282: public void setSelectionList(SelectionList selectionList) {
283: if (selectionList == null) {
284: throw new IllegalArgumentException(
285: "An MultiValueField's selection list cannot be null.");
286: }
287:
288: if (selectionList.getDatatype() != null
289: && selectionList.getDatatype() != definition
290: .getDatatype()) {
291: throw new RuntimeException(
292: "Tried to assign a SelectionList that is not associated with this widget's datatype.");
293: }
294: this .selectionList = selectionList;
295: getForm().addWidgetUpdate(this );
296: }
297:
298: /**
299: * Read this field's selection list from an external source.
300: * All Cocoon-supported protocols can be used.
301: * The format of the XML produced by the source should be the
302: * same as in case of inline specification of the selection list,
303: * thus the root element should be a <code>fd:selection-list</code>
304: * element.
305: * @param uri The URI of the source.
306: */
307: public void setSelectionList(String uri) {
308: setSelectionList(this .definition.buildSelectionList(uri));
309: }
310:
311: /**
312: * Set this field's selection list using values from an in-memory
313: * object. The <code>object</code> parameter should point to a collection
314: * (Java collection or array, or Javascript array) of objects. Each object
315: * belonging to the collection should have a <em>value</em> property and a
316: * <em>label</em> property, whose values are used to specify the <code>value</code>
317: * attribute and the contents of the <code>fd:label</code> child element
318: * of every <code>fd:item</code> in the list.
319: * <p>Access to the values of the above mentioned properties is done
320: * via <a href="http://jakarta.apache.org/commons/jxpath/users-guide.html">XPath</a> expressions.
321: * @param model The collection used as a model for the selection list.
322: * @param valuePath An XPath expression referring to the attribute used
323: * to populate the values of the list's items.
324: * @param labelPath An XPath expression referring to the attribute used
325: * to populate the labels of the list's items.
326: */
327: public void setSelectionList(Object model, String valuePath,
328: String labelPath) {
329: setSelectionList(this .definition.buildSelectionListFromModel(
330: model, valuePath, labelPath));
331: }
332:
333: public void broadcastEvent(WidgetEvent event) {
334: if (event instanceof ValueChangedEvent) {
335: if (this .listener != null) {
336: this .listener.valueChanged((ValueChangedEvent) event);
337: }
338: } else {
339: // Other kinds of events
340: super .broadcastEvent(event);
341: }
342: }
343:
344: public ValidationError getValidationError() {
345: return this .validationError;
346: }
347:
348: public void setValidationError(ValidationError error) {
349: this .validationError = error;
350: getForm().addWidgetUpdate(this );
351: }
352:
353: public Datatype getDatatype() {
354: return definition.getDatatype();
355: }
356:
357: public void addValueChangedListener(ValueChangedListener listener) {
358: this .listener = WidgetEventMulticaster.add(this .listener,
359: listener);
360: }
361:
362: public void removeValueChangedListener(ValueChangedListener listener) {
363: this .listener = WidgetEventMulticaster.remove(this .listener,
364: listener);
365: }
366:
367: public boolean hasValueChangedListeners() {
368: return this.listener != null;
369: }
370: }
|