1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can obtain
10 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
12 * language governing permissions and limitations under the License.
13 *
14 * When distributing the software, include this License Header Notice in each
15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
16 * Sun designates this particular file as subject to the "Classpath" exception
17 * as provided by Sun in the GPL Version 2 section of the License file that
18 * accompanied this code. If applicable, add the following below the License
19 * Header, with the fields enclosed by brackets [] replaced by your own
20 * identifying information: "Portions Copyrighted [year]
21 * [name of copyright owner]"
22 *
23 * Contributor(s):
24 *
25 * If you wish your version of this file to be governed by only the CDDL or
26 * only the GPL Version 2, indicate your decision by adding "[Contributor]
27 * elects to include this software in this distribution under the [CDDL or GPL
28 * Version 2] license." If you don't indicate a single choice of license, a
29 * recipient has the option to distribute your version of this file under
30 * either the CDDL, the GPL Version 2 or to extend the choice of license to
31 * its licensees as provided above. However, if you add GPL Version 2 code
32 * and therefore, elected the GPL Version 2 license, then the option applies
33 * only if the new code is made subject to such option by the copyright
34 * holder.
35 */
36
37 /*
38 * $Id: MenuRenderer.java,v 1.94.4.6 2008/07/08 18:25:53 rlubke Exp $
39 *
40 * (C) Copyright International Business Machines Corp., 2001,2002
41 * The source code for this program is not published or otherwise
42 * divested of its trade secrets, irrespective of what has been
43 * deposited with the U. S. Copyright Office.
44 */
45
46 // MenuRenderer.java
47
48 package com.sun.faces.renderkit.html_basic;
49
50 import java.io.IOException;
51 import java.lang.reflect.Array;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Collection;
55 import java.util.Iterator;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Collections;
59 import java.util.logging.Level;
60
61 import javax.el.ELException;
62 import javax.el.ValueExpression;
63 import javax.faces.component.UIComponent;
64 import javax.faces.component.UISelectMany;
65 import javax.faces.component.UISelectOne;
66 import javax.faces.component.ValueHolder;
67 import javax.faces.context.FacesContext;
68 import javax.faces.context.ResponseWriter;
69 import javax.faces.convert.Converter;
70 import javax.faces.convert.ConverterException;
71 import javax.faces.model.SelectItem;
72 import javax.faces.model.SelectItemGroup;
73
74 import com.sun.faces.RIConstants;
75 import com.sun.faces.renderkit.AttributeManager;
76 import com.sun.faces.renderkit.RenderKitUtils;
77 import com.sun.faces.util.MessageUtils;
78 import com.sun.faces.util.Util;
79 import com.sun.faces.util.RequestStateManager;
80
81 /**
82 * <B>MenuRenderer</B> is a class that renders the current value of
83 * <code>UISelectOne<code> or <code>UISelectMany<code> component as a list of
84 * menu options.
85 */
86
87 public class MenuRenderer extends HtmlBasicInputRenderer {
88
89
90 private static final String[] ATTRIBUTES =
91 AttributeManager.getAttributes(AttributeManager.Key.SELECTMANYMENU);
92
93
94 // ---------------------------------------------------------- Public Methods
95
96
97 public Object convertSelectManyValue(FacesContext context,
98 UISelectMany uiSelectMany,
99 String[] newValues)
100 throws ConverterException {
101
102 // if we have no local value, try to get the valueExpression.
103 ValueExpression valueExpression =
104 uiSelectMany.getValueExpression("value");
105
106 Object result = newValues; // default case, set local value
107 boolean throwException = false;
108
109 // If we have a ValueExpression
110 if (null != valueExpression) {
111 Class modelType = valueExpression.getType(context.getELContext());
112 // Does the valueExpression resolve properly to something with
113 // a type?
114 if(modelType != null) {
115 result = convertSelectManyValuesForModel(context,
116 uiSelectMany,
117 modelType,
118 newValues);
119 }
120 // If it could not be converted, as a fall back try the type of
121 // the valueExpression's current value covering some edge cases such
122 // as where the current value came from a Map.
123 if(result == null) {
124 Object value = valueExpression.getValue(context.getELContext());
125 if(value != null) {
126 result = convertSelectManyValuesForModel(context,
127 uiSelectMany,
128 value.getClass(),
129 newValues);
130 }
131 }
132 if(result == null) {
133 throwException = true;
134 }
135 } else {
136 // No ValueExpression, just use Object array.
137 result = convertSelectManyValues(context, uiSelectMany,
138 Object[].class,
139 newValues);
140 }
141 if (throwException) {
142 StringBuffer values = new StringBuffer();
143 if (null != newValues) {
144 for (int i = 0; i < newValues.length; i++) {
145 if (i == 0) {
146 values.append(newValues[i]);
147 } else {
148 values.append(' ').append(newValues[i]);
149 }
150 }
151 }
152 Object[] params = {
153 values.toString(),
154 valueExpression.getExpressionString()
155 };
156 throw new ConverterException
157 (MessageUtils.getExceptionMessage(MessageUtils.CONVERSION_ERROR_MESSAGE_ID,
158 params));
159 }
160
161 // At this point, result is ready to be set as the value
162 if (logger.isLoggable(Level.FINE)) {
163 logger.fine("SelectMany Component " + uiSelectMany.getId() +
164 " convertedValues " + result);
165 }
166 return result;
167
168 }
169
170
171 public Object convertSelectOneValue(FacesContext context,
172 UISelectOne uiSelectOne,
173 String newValue)
174 throws ConverterException {
175
176 if (RIConstants.NO_VALUE.equals(newValue)) {
177 return null;
178 }
179 if (newValue == null) {
180 if (logger.isLoggable(Level.FINE)) {
181 logger.fine("No conversion necessary for SelectOne Component "
182 + uiSelectOne.getId()
183 + " since the new value is null ");
184 }
185 return null;
186 }
187
188 Object convertedValue =
189 super.getConvertedValue(context, uiSelectOne, newValue);
190 if (logger.isLoggable(Level.FINE)) {
191 logger.fine("SelectOne Component " + uiSelectOne.getId() +
192 " convertedValue " + convertedValue);
193 }
194 return convertedValue;
195
196 }
197
198 @Override
199 public void decode(FacesContext context, UIComponent component) {
200
201 rendererParamsNotNull(context, component);
202
203 if (!shouldDecode(component)) {
204 return;
205 }
206
207 String clientId = component.getClientId(context);
208 assert(clientId != null);
209 // currently we assume the model type to be of type string or
210 // convertible to string and localized by the application.
211 if (component instanceof UISelectMany) {
212 Map<String, String[]> requestParameterValuesMap =
213 context.getExternalContext().
214 getRequestParameterValuesMap();
215 if (requestParameterValuesMap.containsKey(clientId)) {
216 String newValues[] = requestParameterValuesMap.
217 get(clientId);
218 setSubmittedValue(component, newValues);
219 if (logger.isLoggable(Level.FINE)) {
220 logger.fine("submitted values for UISelectMany component "
221 +
222 component.getId()
223 + " after decoding "
224 + Arrays.toString(newValues));
225 }
226 } else {
227 // Use the empty array, not null, to distinguish
228 // between an deselected UISelectMany and a disabled one
229 setSubmittedValue(component, new String[0]);
230 if (logger.isLoggable(Level.FINE)) {
231 logger.fine("Set empty array for UISelectMany component " +
232 component.getId() + " after decoding ");
233 }
234 }
235 } else {
236 // this is a UISelectOne
237 Map<String, String> requestParameterMap =
238 context.getExternalContext().
239 getRequestParameterMap();
240 if (requestParameterMap.containsKey(clientId)) {
241 String newValue = requestParameterMap.get(clientId);
242 setSubmittedValue(component, newValue);
243 if (logger.isLoggable(Level.FINE)) {
244 logger.fine("submitted value for UISelectOne component "
245 +
246 component.getId()
247 + " after decoding "
248 + newValue);
249 }
250
251 } else {
252 // there is no value, but this is different from a null
253 // value.
254 setSubmittedValue(component, RIConstants.NO_VALUE);
255 }
256 }
257
258 }
259
260
261 @Override
262 public void encodeBegin(FacesContext context, UIComponent component)
263 throws IOException {
264
265 rendererParamsNotNull(context, component);
266
267 }
268
269
270 @Override
271 public void encodeEnd(FacesContext context, UIComponent component)
272 throws IOException {
273
274 rendererParamsNotNull(context, component);
275
276 if (!shouldEncode(component)) {
277 return;
278 }
279
280 renderSelect(context, component);
281
282 }
283
284
285 @Override
286 public Object getConvertedValue(FacesContext context, UIComponent component,
287 Object submittedValue)
288 throws ConverterException {
289
290 if (component instanceof UISelectMany) {
291 // need to set the 'TARGET_COMPONENT_ATTRIBUTE_NAME' request attr so the
292 // coerce-value call in the jsf-api UISelectMany.matchValue will work
293 // (need a better way to determine the currently processing UIComponent ...)
294 RequestStateManager.set(context,
295 RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME,
296 component);
297 return convertSelectManyValue(context,
298 ((UISelectMany) component),
299 (String[]) submittedValue);
300 } else {
301 return convertSelectOneValue(context,
302 ((UISelectOne) component),
303 (String) submittedValue);
304 }
305
306 }
307
308 // ------------------------------------------------------- Protected Methods
309
310
311 /*
312 * Converts the provided string array and places them into the correct provided model type.
313 */
314 protected Object convertSelectManyValuesForModel(FacesContext context,
315 UISelectMany uiSelectMany,
316 Class modelType,
317 String[] newValues) {
318 Object result = null;
319 if (modelType.isArray()) {
320 result = convertSelectManyValues(context,
321 uiSelectMany,
322 modelType,
323 newValues);
324 } else if (List.class.isAssignableFrom(modelType)) {
325 Object[] values = (Object[]) convertSelectManyValues(context,
326 uiSelectMany,
327 Object[].class,
328 newValues);
329 // perform a manual copy as the Array returned from
330 // Arrays.asList() isn't mutable. It seems a waste
331 // to also call Collections.addAll(Arrays.asList())
332 List<Object> l = new ArrayList<Object>(values.length);
333 //noinspection ManualArrayToCollectionCopy
334 for (Object v : values) {
335 l.add(v);
336 }
337 result = l;
338 }
339 return result;
340 }
341
342
343 protected Object convertSelectManyValues(FacesContext context,
344 UISelectMany uiSelectMany,
345 Class arrayClass,
346 String[] newValues)
347 throws ConverterException {
348
349 Object result;
350 Converter converter;
351 int len = (null != newValues ? newValues.length : 0);
352
353 Class elementType = arrayClass.getComponentType();
354
355 // Optimization: If the elementType is String, we don't need
356 // conversion. Just return newValues.
357 if (elementType.equals(String.class)) {
358 return newValues;
359 }
360
361 try {
362 result = Array.newInstance(elementType, len);
363 } catch (Exception e) {
364 throw new ConverterException(e);
365 }
366
367 // bail out now if we have no new values, returning our
368 // oh-so-useful zero-length array.
369 if (null == newValues) {
370 return result;
371 }
372
373 // obtain a converter.
374
375 // attached converter takes priority
376 if (null == (converter = uiSelectMany.getConverter())) {
377 // Otherwise, look for a by-type converter
378 if (null == (converter = Util.getConverterForClass(elementType,
379 context))) {
380 // if that fails, and the attached values are of Object type,
381 // we don't need conversion.
382 if (elementType.equals(Object.class)) {
383 return newValues;
384 }
385 StringBuffer valueStr = new StringBuffer();
386 for (int i = 0; i < len; i++) {
387 if (i == 0) {
388 valueStr.append(newValues[i]);
389 } else {
390 valueStr.append(' ').append(newValues[i]);
391 }
392 }
393 Object[] params = {
394 valueStr.toString(),
395 "null Converter"
396 };
397
398 throw new ConverterException(MessageUtils.getExceptionMessage(
399 MessageUtils.CONVERSION_ERROR_MESSAGE_ID, params));
400 }
401 }
402
403 assert(null != result);
404 if (elementType.isPrimitive()) {
405 for (int i = 0; i < len; i++) {
406 if (elementType.equals(Boolean.TYPE)) {
407 Array.setBoolean(result, i,
408 ((Boolean) converter.getAsObject(context,
409 uiSelectMany,
410 newValues[i])));
411 } else if (elementType.equals(Byte.TYPE)) {
412 Array.setByte(result, i,
413 ((Byte) converter.getAsObject(context,
414 uiSelectMany,
415 newValues[i])));
416 } else if (elementType.equals(Double.TYPE)) {
417 Array.setDouble(result, i,
418 ((Double) converter.getAsObject(context,
419 uiSelectMany,
420 newValues[i])));
421 } else if (elementType.equals(Float.TYPE)) {
422 Array.setFloat(result, i,
423 ((Float) converter.getAsObject(context,
424 uiSelectMany,
425 newValues[i])));
426 } else if (elementType.equals(Integer.TYPE)) {
427 Array.setInt(result, i,
428 ((Integer) converter.getAsObject(context,
429 uiSelectMany,
430 newValues[i])));
431 } else if (elementType.equals(Character.TYPE)) {
432 Array.setChar(result, i,
433 ((Character) converter.getAsObject(context,
434 uiSelectMany,
435 newValues[i])));
436 } else if (elementType.equals(Short.TYPE)) {
437 Array.setShort(result, i,
438 ((Short) converter.getAsObject(context,
439 uiSelectMany,
440 newValues[i])));
441 } else if (elementType.equals(Long.TYPE)) {
442 Array.setLong(result, i,
443 ((Long) converter.getAsObject(context,
444 uiSelectMany,
445 newValues[i])));
446 }
447 }
448 } else {
449 for (int i = 0; i < len; i++) {
450 if (logger.isLoggable(Level.FINE)) {
451 Object converted = converter.getAsObject(context,
452 uiSelectMany,
453 newValues[i]);
454 logger.fine("String value: " + newValues[i] +
455 " converts to : " + converted);
456 }
457 Array.set(result, i, converter.getAsObject(context,
458 uiSelectMany,
459 newValues[i]));
460 }
461 }
462 return result;
463
464 }
465
466
467 protected void renderOption(FacesContext context,
468 UIComponent component,
469 Converter converter,
470 SelectItem curItem,
471 Object currentSelections,
472 Object[] submittedValues) throws IOException {
473
474 ResponseWriter writer = context.getResponseWriter();
475 assert (writer != null);
476
477 writer.writeText("\t", component, null);
478 writer.startElement("option", component);
479
480 String valueString = getFormattedValue(context, component,
481 curItem.getValue(), converter);
482 writer.writeAttribute("value", valueString, "value");
483
484 Object valuesArray;
485 Object itemValue;
486 boolean containsValue;
487 if (submittedValues != null) {
488 containsValue = containsaValue(submittedValues);
489 if (containsValue) {
490 valuesArray = submittedValues;
491 itemValue = valueString;
492 } else {
493 valuesArray = currentSelections;
494 itemValue = curItem.getValue();
495 }
496 } else {
497 valuesArray = currentSelections;
498 itemValue = curItem.getValue();
499 }
500
501 if (isSelected(context, itemValue, valuesArray)) {
502 writer.writeAttribute("selected", true, "selected");
503 }
504
505 Boolean disabledAttr =
506 (Boolean) component.getAttributes().get("disabled");
507 boolean componentDisabled = false;
508 if (disabledAttr != null) {
509 if (disabledAttr.equals(Boolean.TRUE)) {
510 componentDisabled = true;
511 }
512 }
513
514 // if the component is disabled, "disabled" attribute would be rendered
515 // on "select" tag, so don't render "disabled" on every option.
516 if ((!componentDisabled) && curItem.isDisabled()) {
517 writer.writeAttribute("disabled", true, "disabled");
518 }
519
520 String labelClass;
521 if (componentDisabled || curItem.isDisabled()) {
522 labelClass = (String) component.
523 getAttributes().get("disabledClass");
524 } else {
525 labelClass = (String) component.
526 getAttributes().get("enabledClass");
527 }
528 if (labelClass != null) {
529 writer.writeAttribute("class", labelClass, "labelClass");
530 }
531
532 if (curItem.isEscape()) {
533 String label = curItem.getLabel();
534 if (label == null) {
535 label = valueString;
536 }
537 writer.writeText(label, component, "label");
538 } else {
539 writer.write(curItem.getLabel());
540 }
541 writer.endElement("option");
542 writer.writeText("\n", component, null);
543
544 }
545
546
547 @Deprecated
548 protected void renderOption(FacesContext context,
549 UIComponent component,
550 Converter converter,
551 SelectItem curItem)
552 throws IOException {
553
554 this.renderOption(context,
555 component,
556 converter,
557 curItem,
558 getCurrentSelectedValues(component),
559 getSubmittedSelectedValues(component));
560
561 }
562
563
564 protected void writeDefaultSize(ResponseWriter writer, int itemCount)
565 throws IOException {
566
567 // if size is not specified default to 1.
568 writer.writeAttribute("size", "1", "size");
569
570 }
571
572
573 protected boolean containsaValue(Object valueArray) {
574
575 if (null != valueArray) {
576 int len = Array.getLength(valueArray);
577 for (int i = 0; i < len; i++) {
578 Object value = Array.get(valueArray, i);
579 if (value != null && !(value.equals(RIConstants.NO_VALUE))) {
580 return true;
581 }
582 }
583 }
584 return false;
585
586 }
587
588
589 protected Object getCurrentSelectedValues(UIComponent component) {
590
591 if (component instanceof UISelectMany) {
592 UISelectMany select = (UISelectMany) component;
593 Object value = select.getValue();
594 if (value instanceof Collection) {
595 return ((Collection) value).toArray();
596 } else if (value != null && !value.getClass().isArray()) {
597 logger.warning(
598 "The UISelectMany value should be an array or a collection type, the actual type is " +
599 value.getClass().getName());
600 }
601
602 return value;
603 }
604
605 UISelectOne select = (UISelectOne) component;
606 Object val = select.getValue();
607 if (val != null) {
608 return new Object[] { val };
609 }
610 return null;
611
612 }
613
614
615 // To derive a selectOne type component from this, override
616 // these methods.
617 protected String getMultipleText(UIComponent component) {
618
619 if (component instanceof UISelectMany) {
620 return " multiple ";
621 }
622 return "";
623
624 }
625
626
627 @Deprecated
628 protected int getOptionNumber(FacesContext context,
629 UIComponent component,
630 List<SelectItem> selectItems) {
631
632
633 return getOptionNumber(selectItems);
634
635 }
636
637
638 protected int getOptionNumber(List<SelectItem> selectItems) {
639
640 int itemCount = 0;
641 if (!selectItems.isEmpty()) {
642 for (Iterator items = selectItems.iterator(); items.hasNext();) {
643 itemCount++;
644 SelectItem item = (SelectItem) items.next();
645 if (item instanceof SelectItemGroup) {
646 int optionsLength =
647 ((SelectItemGroup) item).getSelectItems().length;
648 itemCount += optionsLength;
649 }
650 }
651 }
652 return itemCount;
653
654 }
655
656
657 protected Object[] getSubmittedSelectedValues(UIComponent component) {
658
659 if (component instanceof UISelectMany) {
660 UISelectMany select = (UISelectMany) component;
661 return (Object[]) select.getSubmittedValue();
662 }
663
664 UISelectOne select = (UISelectOne) component;
665 Object val = select.getSubmittedValue();
666 if (val != null) {
667 return new Object[] { val };
668 }
669 return null;
670
671 }
672
673
674 protected boolean isSelected(FacesContext context,
675 Object itemValue,
676 Object valueArray) {
677
678 if (null != valueArray) {
679 if (!valueArray.getClass().isArray()) {
680 logger.warning("valueArray is not an array, the actual type is " +
681 valueArray.getClass());
682 return valueArray.equals(itemValue);
683 }
684 int len = Array.getLength(valueArray);
685 for (int i = 0; i < len; i++) {
686 Object value = Array.get(valueArray, i);
687 if (value == null) {
688 if (itemValue == null) {
689 return true;
690 }
691 } else {
692 Object newValue;
693 if (value.getClass().isInstance(itemValue)) {
694 newValue = itemValue;
695 } else {
696 try {
697 newValue =
698 context.getApplication()
699 .getExpressionFactory().
700 coerceToType(itemValue, value.getClass());
701 } catch (ELException ele) {
702 newValue = itemValue;
703 } catch (IllegalArgumentException iae) {
704 // If coerceToType fails, per the docs it should throw
705 // an ELException, however, GF 9.0 and 9.0u1 will throw
706 // an IllegalArgumentException instead (see GF issue 1527).
707 newValue = itemValue;
708 }
709 }
710 if (value.equals(newValue)) {
711 return true;
712 }
713 }
714 }
715 }
716 return false;
717
718 }
719
720
721 protected void renderOptions(FacesContext context,
722 UIComponent component,
723 List<SelectItem> items)
724 throws IOException {
725
726 ResponseWriter writer = context.getResponseWriter();
727 assert(writer != null);
728
729 Converter converter = null;
730 if(component instanceof ValueHolder) {
731 converter = ((ValueHolder)component).getConverter();
732 }
733
734 if (!items.isEmpty()) {
735 Object currentSelections = getCurrentSelectedValues(component);
736 Object[] submittedValues = getSubmittedSelectedValues(component);
737 RequestStateManager.set(context,
738 RequestStateManager.TARGET_COMPONENT_ATTRIBUTE_NAME,
739 component);
740 for (SelectItem item : items) {
741 if (item instanceof SelectItemGroup) {
742 // render OPTGROUP
743 writer.startElement("optgroup", component);
744 writer.writeAttribute("label", item.getLabel(), "label");
745
746 // if the component is disabled, "disabled" attribute would be rendered
747 // on "select" tag, so don't render "disabled" on every option.
748 boolean componentDisabled =
749 Boolean.TRUE.equals(component.getAttributes().get("disabled"));
750 if ((!componentDisabled) && item.isDisabled()) {
751 writer.writeAttribute("disabled", true, "disabled");
752 }
753
754
755 // render options of this group.
756 SelectItem[] itemsArray =
757 ((SelectItemGroup) item).getSelectItems();
758 for (int i = 0; i < itemsArray.length; ++i) {
759 renderOption(context,
760 component,
761 converter,
762 itemsArray[i],
763 currentSelections,
764 submittedValues);
765 }
766 writer.endElement("optgroup");
767 } else {
768 renderOption(context,
769 component,
770 converter,
771 item,
772 currentSelections,
773 submittedValues);
774 }
775 }
776 }
777
778 }
779
780
781 // Render the "select" portion..
782 //
783 protected void renderSelect(FacesContext context,
784 UIComponent component) throws IOException {
785
786 ResponseWriter writer = context.getResponseWriter();
787 assert(writer != null);
788
789 if (logger.isLoggable(Level.FINER)) {
790 logger.log(Level.FINER, "Rendering 'select'");
791 }
792 writer.startElement("select", component);
793 writeIdAttributeIfNecessary(context, writer, component);
794 writer.writeAttribute("name", component.getClientId(context),
795 "clientId");
796 // render styleClass attribute if present.
797 String styleClass;
798 if (null !=
799 (styleClass =
800 (String) component.getAttributes().get("styleClass"))) {
801 writer.writeAttribute("class", styleClass, "styleClass");
802 }
803 if (!getMultipleText(component).equals("")) {
804 writer.writeAttribute("multiple", true, "multiple");
805 }
806
807 // Determine how many option(s) we need to render, and update
808 // the component's "size" attribute accordingly; The "size"
809 // attribute will be rendered as one of the "pass thru" attributes
810 List<SelectItem> items = RenderKitUtils.getSelectItems(context, component);
811 int itemCount = getOptionNumber(items);
812 if (logger.isLoggable(Level.FINE)) {
813 logger.fine("Rendering " + itemCount + " options");
814 }
815 // If "size" is *not* set explicitly, we have to default it correctly
816 Integer size = (Integer) component.getAttributes().get("size");
817 if (size == null || size == Integer.MIN_VALUE) {
818 size = itemCount;
819 }
820 writeDefaultSize(writer, size);
821
822 RenderKitUtils.renderPassThruAttributes(writer,
823 component,
824 ATTRIBUTES);
825 RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer,
826 component);
827 // Now, render the "options" portion...
828 renderOptions(context, component, items);
829
830 writer.endElement("select");
831
832 }
833
834 } // end of class MenuRenderer