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.repeater; 018 019import java.util.HashSet; 020import java.util.Iterator; 021import java.util.Set; 022 023import org.apache.wicket.Component; 024import org.apache.wicket.DequeueContext; 025import org.apache.wicket.DequeueContext.Bookmark; 026import org.apache.wicket.MarkupContainer; 027import org.apache.wicket.markup.IMarkupFragment; 028import org.apache.wicket.markup.html.WebMarkupContainer; 029import org.apache.wicket.model.IModel; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * Base class for repeaters. This container renders each of its children using its own markup. 035 * 036 * The children are collected using {@link #renderIterator()} method. This class will take care of 037 * properly positioning and rewinding its markup stream so before each child renders it points to 038 * the beginning of this component. Each child is rendered by a call to 039 * {@link #renderChild(Component)}. A typical implementation simply does 040 * <code>child.render();</code>. 041 * 042 * <strong>Note</strong>: the children are added during the render phase (in {@linkplain #beforeRender()} so 043 * most of the specializations of this class should not be used as parents of 044 * {@link org.apache.wicket.markup.html.form.FormComponent}s in stateless pages because the form components 045 * will not be available during the action phase (i.e. at 046 * {@link org.apache.wicket.markup.html.form.StatelessForm#onSubmit()}). Use 047 * {@link org.apache.wicket.markup.repeater.RepeatingView} in these cases. 048 * 049 * @author Igor Vaynberg (ivaynberg) 050 */ 051public abstract class AbstractRepeater extends WebMarkupContainer 052{ 053 private static final long serialVersionUID = 1L; 054 055 private static final Logger log = LoggerFactory.getLogger(AbstractRepeater.class); 056 057 /** 058 * Constructor 059 * 060 * @param id 061 */ 062 public AbstractRepeater(String id) 063 { 064 super(id); 065 } 066 067 /** 068 * Constructor 069 * 070 * @param id 071 * @param model 072 */ 073 public AbstractRepeater(String id, IModel<?> model) 074 { 075 super(id, model); 076 } 077 078 /** 079 * Returns an iterator for the collection of child components to be rendered. Users can override 080 * this to change order of rendered children. 081 * 082 * @return iterator over child components to be rendered 083 */ 084 protected abstract Iterator<? extends Component> renderIterator(); 085 086 /** 087 * Renders all child items in no specified order 088 */ 089 @Override 090 protected final void onRender() 091 { 092 Iterator<? extends Component> it = renderIterator(); 093 while (it.hasNext()) 094 { 095 Component child = it.next(); 096 if (child == null) 097 { 098 throw new IllegalStateException( 099 "The render iterator returned null for a child. Container: " + this.toString() + 100 "; Iterator=" + it.toString()); 101 } 102 renderChild(child); 103 } 104 } 105 106 /** 107 * Render a single child. This method can be overridden to modify how a single child component 108 * is rendered. 109 * 110 * @param child 111 * Child component to be rendered 112 */ 113 protected void renderChild(final Component child) 114 { 115 child.render(); 116 } 117 118 /** 119 * @see org.apache.wicket.Component#onBeforeRender() 120 */ 121 @Override 122 protected void onBeforeRender() 123 { 124 onPopulate(); 125 126 if (getApplication().usesDevelopmentConfig()) 127 { 128 Set<String> usedComponentIds = new HashSet<>(); 129 Iterator<? extends Component> i = iterator(); 130 while (i.hasNext()) 131 { 132 Component c = i.next(); 133 String componentId = c.getId(); 134 if (usedComponentIds.add(componentId) == false) 135 { 136 log.warn("Repeater '{}' has multiple children with the same component id: '{}'", 137 getPageRelativePath(), componentId); 138 // do not flood the log 139 break; 140 } 141 } 142 } 143 super.onBeforeRender(); 144 } 145 146 /** 147 * @see org.apache.wicket.MarkupContainer#getMarkup(org.apache.wicket.Component) 148 */ 149 @Override 150 public IMarkupFragment getMarkup(final Component child) 151 { 152 // each direct child gets the markup of this repeater 153 return getMarkup(); 154 } 155 156 /** 157 * Callback to let the repeater know it should populate itself with its items. 158 */ 159 protected abstract void onPopulate(); 160 161 @Override 162 public void dequeue(DequeueContext dequeue) 163 { 164 if (size() > 0) 165 { 166 // essentially what we do is for every child replace the repeater with the child in 167 // dequeue container stack and run the dequeue on the child. we also take care to reset 168 // the state of the dequeue context after we process every child. 169 170 Bookmark bookmark = dequeue.save(); 171 172 for (Component child : this) 173 { 174 if (child instanceof MarkupContainer) 175 { 176 dequeue.popContainer(); // pop the repeater 177 MarkupContainer container = (MarkupContainer) child; 178 dequeue.pushContainer(container); 179 container.dequeue(dequeue); 180 dequeue.restore(bookmark); 181 } 182 } 183 } 184 185 dequeue.skipToCloseTag(); 186 187 } 188}