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.markup.html.internal; 018 019import java.util.Iterator; 020 021import org.apache.wicket.Component; 022import org.apache.wicket.MarkupContainer; 023import org.apache.wicket.WicketRuntimeException; 024import org.apache.wicket.markup.ComponentTag; 025import org.apache.wicket.markup.MarkupException; 026import org.apache.wicket.markup.MarkupStream; 027import org.apache.wicket.markup.html.TransparentWebMarkupContainer; 028import org.apache.wicket.markup.html.WebMarkupContainer; 029import org.apache.wicket.markup.html.basic.EnclosureContainer; 030import org.apache.wicket.markup.parser.filter.EnclosureHandler; 031import org.apache.wicket.markup.resolver.ComponentResolvers; 032import org.apache.wicket.markup.resolver.ComponentResolvers.ResolverFilter; 033import org.apache.wicket.markup.resolver.IComponentResolver; 034import org.apache.wicket.util.string.Strings; 035 036 037/** 038 * An Enclosure are automatically created by Wicket. Do not create it yourself. An Enclosure 039 * container is created if <wicket:enclosure> is found in the markup. It is meant to solve the 040 * following situation. Instead of 041 * 042 * <pre> 043 * <table wicket:id="label-container" class="notify"><tr><td><span wicket:id="label">[[notification]]</span></td></tr></table> 044 * WebMarkupContainer container=new WebMarkupContainer("label-container") 045 * { 046 * public boolean isVisible() 047 * { 048 * return hasNotification(); 049 * } 050 * }; 051 * add(container); 052 * container.add(new Label("label", notificationModel)); 053 * </pre> 054 * 055 * with Enclosure you are able to do the following: 056 * 057 * <pre> 058 * <wicket:enclosure> 059 * <table class="notify"><tr><td><span wicket:id="label">[[notification]]</span></td></tr></table> 060 * </wicket:enclosure> 061 * add(new Label("label", notificationModel)) 062 * { 063 * public boolean isVisible() 064 * { 065 * return hasNotification(); 066 * } 067 * } 068 * </pre> 069 * <p> 070 * Please note that since a transparent auto-component is created for the tag, the markup and the 071 * component hierarchy will not be in sync which leads to subtle differences if your code relies on 072 * onBeforeRender() and validate() being called for the children inside the enclosure tag. E.g. it 073 * might happen that onBeforeRender() and validate() gets called on invisible components. In doubt, 074 * please fall back to {@link EnclosureContainer}. 075 * </p> 076 * <p> 077 * Additionally due to the reason above it is not possible to assert that children in Enclosure are 078 * not visible to WicketTester. 079 * </p> 080 * 081 * @see EnclosureHandler 082 * @see EnclosureContainer 083 * 084 * @author igor 085 * @author Juergen Donnerstag 086 * @since 1.3 087 */ 088public class Enclosure extends WebMarkupContainer implements IComponentResolver 089{ 090 private static final long serialVersionUID = 1L; 091 092 /** The child component to delegate the isVisible() call to */ 093 private Component childComponent; 094 095 /** Id of the child component that will control visibility of the enclosure */ 096 private final String childId; 097 098 /** 099 * Construct. 100 * 101 * @param id 102 * @param childId 103 */ 104 public Enclosure(final String id, final String childId) 105 { 106 super(id); 107 108 if (childId == null) 109 { 110 throw new MarkupException( 111 "You most likely forgot to register the EnclosureHandler with the MarkupParserFactory"); 112 } 113 114 this.childId = childId; 115 } 116 117 /** 118 * 119 * @return child id 120 */ 121 public final String getChildId() 122 { 123 return childId.toString(); 124 } 125 126 protected final Component getChild() 127 { 128 if (childComponent == null) 129 { 130 // try to find child when queued 131 childComponent = resolveChild(this); 132 } 133 if (childComponent == null) 134 { 135 // try to find child when resolved 136 childComponent = getChildComponent(new MarkupStream(getMarkup()), getEnclosureParent()); 137 } 138 return childComponent; 139 } 140 141 /** 142 * Searches for the controlling child component looking also 143 * through transparent components. 144 * 145 * @param container 146 * the current container 147 * @return the controlling child component, null if no one is found 148 */ 149 private Component resolveChild(MarkupContainer container) 150 { 151 Component childController = container.get(childId); 152 153 Iterator<Component> children = container.iterator(); 154 155 while (children.hasNext() && childController == null) 156 { 157 Component transparentChild = children.next(); 158 159 if(transparentChild instanceof TransparentWebMarkupContainer) 160 { 161 childController = resolveChild((MarkupContainer)transparentChild); 162 } 163 } 164 165 return childController; 166 } 167 168 @Override 169 public boolean isVisible() 170 { 171 return getChild().determineVisibility(); 172 } 173 174 @Override 175 protected void onConfigure() 176 { 177 super.onConfigure(); 178 final Component child = getChild(); 179 180 child.configure(); 181 boolean childVisible = child.determineVisibility(); 182 183 setVisible(childVisible); 184 } 185 186 @Override 187 protected void onDetach() 188 { 189 super.onDetach(); 190 191 // necessary when queued and lives with the page instead of just during render 192 childComponent = null; 193 } 194 195 /** 196 * Get the real parent container 197 * 198 * @return enclosure's parent markup container 199 */ 200 protected MarkupContainer getEnclosureParent() 201 { 202 MarkupContainer parent = getParent(); 203 204 if (parent == null) 205 { 206 throw new WicketRuntimeException( 207 "Unable to find parent component which is not a transparent resolver"); 208 } 209 return parent; 210 } 211 212 /** 213 * Resolves the child component which is the controller of this Enclosure 214 * 215 * @param markupStream 216 * the markup stream of this Enclosure 217 * @param enclosureParent 218 * the non-auto parent component of this Enclosure 219 * @return The component associated with the {@linkplain #childId} 220 */ 221 private Component getChildComponent(final MarkupStream markupStream, 222 MarkupContainer enclosureParent) 223 { 224 String fullChildId = getChildId(); 225 226 Component controller = enclosureParent.get(fullChildId); 227 if (controller == null) 228 { 229 int orgIndex = markupStream.getCurrentIndex(); 230 try 231 { 232 while (markupStream.isCurrentIndexInsideTheStream()) 233 { 234 markupStream.next(); 235 if (markupStream.skipUntil(ComponentTag.class)) 236 { 237 ComponentTag tag = markupStream.getTag(); 238 if ((tag != null) && (tag.isOpen() || tag.isOpenClose())) 239 { 240 String tagId = tag.getId(); 241 242 if (fullChildId.equals(tagId)) 243 { 244 ComponentTag fullComponentTag = new ComponentTag(tag); 245 fullComponentTag.setId(childId.toString()); 246 247 controller = ComponentResolvers.resolve(enclosureParent, 248 markupStream, fullComponentTag, new ResolverFilter() 249 { 250 @Override 251 public boolean ignoreResolver( 252 final IComponentResolver resolver) 253 { 254 return resolver instanceof EnclosureHandler; 255 } 256 }); 257 break; 258 } 259 else if (fullChildId.startsWith(tagId + PATH_SEPARATOR)) 260 { 261 fullChildId = Strings.afterFirst(fullChildId, PATH_SEPARATOR); 262 } 263 } 264 } 265 } 266 } 267 finally 268 { 269 markupStream.setCurrentIndex(orgIndex); 270 } 271 } 272 273 checkChildComponent(controller); 274 return controller; 275 } 276 277 @Override 278 public Component resolve(MarkupContainer container, MarkupStream markupStream, ComponentTag tag) 279 { 280 if (childId.equals(tag.getId())) 281 { 282 return childComponent; 283 } 284 return getEnclosureParent().get(tag.getId()); 285 } 286 287 /** 288 * 289 * @param controller 290 */ 291 private void checkChildComponent(final Component controller) 292 { 293 if (controller == null) 294 { 295 throw new WicketRuntimeException("Could not find child with id: " + childId + 296 " in the wicket:enclosure"); 297 } 298 else if (controller == this) 299 { 300 throw new WicketRuntimeException( 301 "Programming error: childComponent == enclose component; endless loop"); 302 } 303 } 304}