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.wicket.markup.html.form;
018:
019: import java.io.Serializable;
020: import java.util.ArrayList;
021: import java.util.List;
022:
023: import org.apache.wicket.markup.ComponentTag;
024: import org.apache.wicket.markup.MarkupStream;
025: import org.apache.wicket.model.IModel;
026: import org.apache.wicket.model.Model;
027: import org.apache.wicket.util.string.AppendingStringBuffer;
028: import org.apache.wicket.util.string.Strings;
029: import org.apache.wicket.version.undo.Change;
030:
031: /**
032: * Abstract base class for all choice (html select) options.
033: *
034: * @author Jonathan Locke
035: * @author Eelco Hillenius
036: * @author Johan Compagner
037: */
038: abstract class AbstractChoice extends FormComponent {
039: /** The list of objects. */
040: private IModel choices;
041:
042: /** The renderer used to generate display/id values for the objects. */
043: private IChoiceRenderer renderer;
044:
045: /**
046: * Constructor.
047: *
048: * @param id
049: * See Component
050: * @see org.apache.wicket.Component#Component(String)
051: */
052: public AbstractChoice(final String id) {
053: this (id, new Model(new ArrayList()), new ChoiceRenderer());
054: }
055:
056: /**
057: * Constructor.
058: *
059: * @param id
060: * See Component
061: * @param choices
062: * The collection of choices in the dropdown
063: * @see org.apache.wicket.Component#Component(String)
064: */
065: public AbstractChoice(final String id, final List choices) {
066: this (id, new Model((Serializable) choices),
067: new ChoiceRenderer());
068: }
069:
070: /**
071: * Constructor.
072: *
073: * @param id
074: * See Component
075: * @param renderer
076: * The rendering engine
077: * @param choices
078: * The collection of choices in the dropdown
079: * @see org.apache.wicket.Component#Component(String)
080: */
081: public AbstractChoice(final String id, final List choices,
082: final IChoiceRenderer renderer) {
083: this (id, new Model((Serializable) choices), renderer);
084: }
085:
086: /**
087: * Constructor.
088: *
089: * @param id
090: * See Component
091: * @param model
092: * See Component
093: * @param choices
094: * The collection of choices in the dropdown
095: * @see org.apache.wicket.Component#Component(String, IModel)
096: */
097: public AbstractChoice(final String id, IModel model,
098: final List choices) {
099: this (id, model, new Model((Serializable) choices),
100: new ChoiceRenderer());
101: }
102:
103: /**
104: * Constructor.
105: *
106: * @param id
107: * See Component
108: * @param model
109: * See Component
110: * @param choices
111: * The drop down choices
112: * @param renderer
113: * The rendering engine
114: * @see org.apache.wicket.Component#Component(String, IModel)
115: */
116: public AbstractChoice(final String id, IModel model,
117: final List choices, final IChoiceRenderer renderer) {
118: this (id, model, new Model((Serializable) choices), renderer);
119: }
120:
121: /**
122: * Constructor.
123: *
124: * @param id
125: * See Component
126: * @param choices
127: * The collection of choices in the dropdown
128: * @see org.apache.wicket.Component#Component(String)
129: */
130: public AbstractChoice(final String id, final IModel choices) {
131: this (id, choices, new ChoiceRenderer());
132: }
133:
134: /**
135: * Constructor.
136: *
137: * @param id
138: * See Component
139: * @param renderer
140: * The rendering engine
141: * @param choices
142: * The collection of choices in the dropdown
143: * @see org.apache.wicket.Component#Component(String)
144: */
145: public AbstractChoice(final String id, final IModel choices,
146: final IChoiceRenderer renderer) {
147: super (id);
148: this .choices = wrap(choices);
149: setChoiceRenderer(renderer);
150: }
151:
152: /**
153: * Constructor.
154: *
155: * @param id
156: * See Component
157: * @param model
158: * See Component
159: * @param choices
160: * The collection of choices in the dropdown
161: * @see org.apache.wicket.Component#Component(String, IModel)
162: */
163: public AbstractChoice(final String id, IModel model,
164: final IModel choices) {
165: this (id, model, choices, new ChoiceRenderer());
166: }
167:
168: /**
169: * Constructor.
170: *
171: * @param id
172: * See Component
173: * @param model
174: * See Component
175: * @param renderer
176: * The rendering engine
177: * @param choices
178: * The drop down choices
179: * @see org.apache.wicket.Component#Component(String, IModel)
180: */
181: public AbstractChoice(final String id, IModel model,
182: final IModel choices, final IChoiceRenderer renderer) {
183: super (id, model);
184: this .choices = wrap(choices);
185: setChoiceRenderer(renderer);
186: }
187:
188: /**
189: * @return The collection of object that this choice has
190: */
191: public List getChoices() {
192: List choices = (this .choices != null) ? (List) this .choices
193: .getObject() : null;
194: if (choices == null) {
195: throw new NullPointerException(
196: "List of choices is null - Was the supplied 'Choices' model empty?");
197: }
198: return choices;
199: }
200:
201: /**
202: * Sets the list of choices
203: *
204: * @param choices
205: * model representing the list of choices
206: * @return this for chaining
207: */
208: public final AbstractChoice setChoices(IModel choices) {
209: if (this .choices != null && this .choices != choices) {
210: if (isVersioned()) {
211: addStateChange(new ChoicesListChange());
212: }
213: }
214: this .choices = wrap(choices);
215: return this ;
216: }
217:
218: /**
219: * Sets the list of choices.
220: *
221: * @param choices
222: * the list of choices
223: * @return this for chaining
224: */
225: public final AbstractChoice setChoices(List choices) {
226: if ((this .choices != null)) {
227: if (isVersioned()) {
228: addStateChange(new ChoicesListChange());
229: }
230: }
231: this .choices = new Model((Serializable) choices);
232: return this ;
233: }
234:
235: /**
236: * @return The IChoiceRenderer used for rendering the data objects
237: */
238: public final IChoiceRenderer getChoiceRenderer() {
239: return renderer;
240: }
241:
242: /**
243: * Set the choice renderer to be used.
244: *
245: * @param renderer
246: * @return this for chaining
247: */
248: public final AbstractChoice setChoiceRenderer(
249: IChoiceRenderer renderer) {
250: if (renderer == null) {
251: renderer = new ChoiceRenderer();
252: }
253: this .renderer = renderer;
254: return this ;
255: }
256:
257: /**
258: * @see org.apache.wicket.Component#detachModel()
259: */
260: protected void detachModel() {
261: super .detachModel();
262:
263: if (choices != null) {
264: choices.detach();
265: }
266: }
267:
268: /**
269: *
270: * @param selected
271: * The object that's currently selected
272: * @return Any default choice, such as "Choose One", depending on the
273: * subclass
274: */
275: protected CharSequence getDefaultChoice(final Object selected) {
276: return "";
277: }
278:
279: /**
280: * Gets whether the given value represents the current selection.
281: *
282: * @param object
283: * The object to check
284: * @param index
285: * The index in the choices collection this object is in.
286: * @param selected
287: * The currently selected string value
288: * @return Whether the given value represents the current selection
289: */
290: protected abstract boolean isSelected(final Object object,
291: int index, String selected);
292:
293: /**
294: * Gets whether the given value is disabled. This default implementation
295: * always returns false.
296: *
297: * @param object
298: * The object to check
299: * @param index
300: * The index in the choices collection this object is in.
301: * @param selected
302: * The currently selected string value
303: * @return Whether the given value represents the current selection
304: */
305: protected boolean isDisabled(final Object object, int index,
306: String selected) {
307: return false;
308: }
309:
310: /**
311: * Handle the container's body.
312: *
313: * @param markupStream
314: * The markup stream
315: * @param openTag
316: * The open tag for the body
317: * @see org.apache.wicket.Component#onComponentTagBody(MarkupStream,
318: * ComponentTag)
319: */
320: protected void onComponentTagBody(final MarkupStream markupStream,
321: final ComponentTag openTag) {
322: List choices = getChoices();
323: final AppendingStringBuffer buffer = new AppendingStringBuffer(
324: (choices.size() * 50) + 16);
325: final String selected = getValue();
326:
327: // Append default option
328: buffer.append(getDefaultChoice(selected));
329:
330: for (int index = 0; index < choices.size(); index++) {
331: final Object choice = choices.get(index);
332: appendOptionHtml(buffer, choice, index, selected);
333: }
334:
335: buffer.append("\n");
336: replaceComponentTagBody(markupStream, openTag, buffer);
337: }
338:
339: /**
340: * Generats and appends html for a single choice into the provided buffer
341: *
342: * @param buffer
343: * Appending string buffer that will have the generated html
344: * appended
345: * @param choice
346: * Choice object
347: * @param index
348: * The index of this option
349: * @param selected
350: * The currently selected string value
351: */
352: protected void appendOptionHtml(AppendingStringBuffer buffer,
353: Object choice, int index, String selected) {
354: Object objectValue = renderer.getDisplayValue(choice);
355: Class objectClass = objectValue == null ? null : objectValue
356: .getClass();
357: final String displayValue = getConverter(objectClass)
358: .convertToString(objectValue, getLocale());
359: buffer.append("\n<option ");
360: if (isSelected(choice, index, selected)) {
361: buffer.append("selected=\"selected\" ");
362: }
363: if (isDisabled(choice, index, selected)) {
364: buffer.append("disabled=\"disabled\" ");
365: }
366: buffer.append("value=\"");
367: buffer.append(renderer.getIdValue(choice, index));
368: buffer.append("\">");
369:
370: String display = displayValue;
371: if (localizeDisplayValues()) {
372: display = getLocalizer().getString(displayValue, this ,
373: displayValue);
374: }
375: CharSequence escaped = Strings.escapeMarkup(display, false,
376: true);
377: buffer.append(escaped);
378: buffer.append("</option>");
379: }
380:
381: /**
382: * @see org.apache.wicket.markup.html.form.FormComponent#supportsPersistence()
383: */
384: protected boolean supportsPersistence() {
385: return true;
386: }
387:
388: /**
389: * Override this method if you want to localize the display values of the
390: * generated options. By default false is returned so that the display
391: * values of options are not tested if they have a i18n key.
392: *
393: * @return true If you want to localize the display values, default == false
394: */
395: protected boolean localizeDisplayValues() {
396: return false;
397: }
398:
399: /**
400: * Change object to represent the change of the choices property
401: *
402: * @author ivaynberg
403: */
404: private class ChoicesListChange extends Change {
405: private static final long serialVersionUID = 1L;
406:
407: private final IModel oldChoices;
408:
409: /**
410: * Construct.
411: */
412: public ChoicesListChange() {
413: oldChoices = choices;
414: }
415:
416: /**
417: * @see org.apache.wicket.version.undo.Change#undo()
418: */
419: public void undo() {
420: choices = oldChoices;
421: }
422:
423: /**
424: * Make debugging easier
425: *
426: * @see java.lang.Object#toString()
427: */
428: public String toString() {
429: return "ChoiceListChange[component: " + getPath()
430: + ", old choices: " + oldChoices + "]";
431: }
432:
433: }
434:
435: }
|