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