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 */ 017package org.apache.wicket.extensions.markup.html.form.select; 018 019import java.util.Collection; 020 021import org.apache.wicket.markup.ComponentTag; 022import org.apache.wicket.markup.MarkupStream; 023import org.apache.wicket.markup.html.WebMarkupContainer; 024import org.apache.wicket.markup.parser.XmlTag.TagType; 025import org.apache.wicket.markup.repeater.RepeatingView; 026import org.apache.wicket.model.IModel; 027import org.apache.wicket.model.util.CollectionModel; 028import org.apache.wicket.util.string.Strings; 029import org.slf4j.Logger; 030import org.slf4j.LoggerFactory; 031 032 033/** 034 * Component that makes it easy to produce a list of SelectOption components. 035 * <p> 036 * Has to be attached to a <option> markup tag. 037 * <p> 038 * Note: The following pre Wicket 9 markup is deprecated and results in a log warning. Its support 039 * will be removed in Wicket 10: 040 * 041 * <pre> 042 * <code> 043 * <wicket:container wicket:id="selectOptions"><option wicket:id="option"></option></wicket:container> 044 * </code> 045 * </pre> 046 * 047 * @param <T> 048 * type of elements contained in the model's collection 049 * @author Igor Vaynberg (ivaynberg) 050 */ 051public class SelectOptions<T> extends RepeatingView 052{ 053 private static final long serialVersionUID = 1L; 054 055 private static final Logger log = LoggerFactory.getLogger(SelectOptions.class); 056 057 private boolean recreateChoices = false; 058 059 private final IOptionRenderer<T> renderer; 060 061 /** 062 * Constructor 063 * 064 * @param id 065 * @param model 066 * @param renderer 067 */ 068 public SelectOptions(final String id, final IModel<? extends Collection<? extends T>> model, 069 final IOptionRenderer<T> renderer) 070 { 071 super(id, model); 072 this.renderer = renderer; 073 setRenderBodyOnly(true); 074 } 075 076 /** 077 * Constructor 078 * 079 * @param id 080 * @param elements 081 * @param renderer 082 */ 083 public SelectOptions(final String id, final Collection<? extends T> elements, 084 final IOptionRenderer<T> renderer) 085 { 086 this(id, new CollectionModel<>(elements), renderer); 087 } 088 089 /** 090 * Controls whether {@link SelectOption}s are recreated on each render. 091 * <p> 092 * Note: When recreating on each render, {@link #newOption(String, IModel)} should return 093 * {@link SelectOption}s with stable values, i.e. {@link SelectOption#getValue()} should return 094 * a value based on its model object instead of the default auto index. Otherwise the current 095 * selection will be lost on form errors. 096 * 097 * @param refresh 098 * @return this for chaining 099 * 100 * @see SelectOption#getValue() 101 */ 102 public SelectOptions<T> setRecreateChoices(final boolean refresh) 103 { 104 recreateChoices = refresh; 105 return this; 106 } 107 108 /** 109 * {@inheritDoc} 110 */ 111 @SuppressWarnings("unchecked") 112 @Override 113 protected final void onPopulate() 114 { 115 if ((size() == 0) || recreateChoices) 116 { 117 // populate this repeating view with SelectOption components 118 removeAll(); 119 120 Collection<? extends T> modelObject = (Collection<? extends T>)getDefaultModelObject(); 121 if (modelObject != null) 122 { 123 // TODO remove in Wicket 10 124 boolean option = "option".equalsIgnoreCase(getMarkupTag().getName()); 125 if (option == false) 126 { 127 log.warn("Since version 9.0.0 you should use an option tag"); 128 } 129 130 for (T value : modelObject) 131 { 132 // we add our actual SelectOption component to the row 133 String text = renderer.getDisplayValue(value); 134 IModel<T> model = renderer.getModel(value); 135 136 if (option) 137 { 138 add(newOption(newChildId(), text, model)); 139 } 140 else 141 { 142 // pre Wicket 9 a container is used to represent a row in repeater 143 WebMarkupContainer row = new WebMarkupContainer(newChildId()); 144 row.setRenderBodyOnly(true); 145 add(row); 146 147 // we add our actual SelectOption component to the row 148 row.add(newOption(text, model)); 149 } 150 } 151 } 152 } 153 } 154 155 /** 156 * @deprecated override {@link #newOption(String, String, IModel)} instead. 157 */ 158 protected SelectOption<T> newOption(final String text, final IModel<T> model) 159 { 160 return newOption("option", text, model); 161 } 162 163 /** 164 * Factory method for creating a new <code>SelectOption</code>. Override to add your own 165 * extensions, such as Ajax behaviors. 166 * 167 * @param id 168 * component id 169 * @param text 170 * @param model 171 * @return a {@link SelectOption} 172 */ 173 protected SelectOption<T> newOption(final String id, final String text, final IModel<T> model) 174 { 175 SimpleSelectOption<T> option = new SimpleSelectOption<>(id, model, text); 176 option.setEscapeModelStrings(this.getEscapeModelStrings()); 177 return option; 178 } 179 180 /** 181 * 182 * @param <V> 183 */ 184 private static class SimpleSelectOption<V> extends SelectOption<V> 185 { 186 private static final long serialVersionUID = 1L; 187 188 private final String text; 189 190 /** 191 * @param id 192 * @param model 193 * @param text 194 */ 195 public SimpleSelectOption(final String id, final IModel<V> model, final String text) 196 { 197 super(id, model); 198 this.text = text; 199 } 200 201 /** 202 * {@inheritDoc} 203 */ 204 @Override 205 public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag) 206 { 207 CharSequence escaped = text; 208 if (getEscapeModelStrings()) 209 { 210 escaped = Strings.escapeMarkup(text); 211 } 212 213 replaceComponentTagBody(markupStream, openTag, escaped); 214 } 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override 220 protected void onComponentTag(ComponentTag tag) 221 { 222 super.onComponentTag(tag); 223 224 // always transform the tag to <label></label> so even markup defined as <label/> 225 // render 226 tag.setType(TagType.OPEN); 227 } 228 } 229 230 @Override 231 protected void onDetach() 232 { 233 renderer.detach(); 234 235 super.onDetach(); 236 } 237}