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.repeater.tree; 018 019import java.util.Set; 020 021import org.apache.wicket.Component; 022import org.apache.wicket.IGenericComponent; 023import org.apache.wicket.ajax.AjaxRequestTarget; 024import org.apache.wicket.core.request.handler.IPartialPageRequestHandler; 025import org.apache.wicket.extensions.markup.html.repeater.util.ProviderSubset; 026import org.apache.wicket.markup.html.panel.Panel; 027import org.apache.wicket.markup.repeater.DefaultItemReuseStrategy; 028import org.apache.wicket.markup.repeater.IItemReuseStrategy; 029import org.apache.wicket.markup.repeater.Item; 030import org.apache.wicket.model.IModel; 031 032/** 033 * Abstract base class for {@link NestedTree} and {@link TableTree}. Uses its model for storing the 034 * {@link State} of its nodes. 035 * 036 * Note that a tree has no notion of a <em>selection</em>. Handling state of nodes besides 037 * expanse/collapse is irrelevant to a tree implementation. 038 * 039 * @see #newContentComponent(String, IModel) 040 * 041 * @author svenmeier 042 * @param <T> 043 * the node type 044 */ 045public abstract class AbstractTree<T> extends Panel implements IGenericComponent<Set<T>, AbstractTree<T>> 046{ 047 private static final long serialVersionUID = 1L; 048 049 private ITreeProvider<T> provider; 050 051 private IItemReuseStrategy itemReuseStrategy; 052 053 protected AbstractTree(String id, ITreeProvider<T> provider) 054 { 055 this(id, provider, null); 056 } 057 058 protected AbstractTree(String id, ITreeProvider<T> provider, IModel<? extends Set<T>> state) 059 { 060 super(id, state); 061 062 if (provider == null) 063 { 064 throw new IllegalArgumentException("argument [provider] cannot be null"); 065 } 066 this.provider = provider; 067 } 068 069 /** 070 * Sets the item reuse strategy. This strategy controls the creation of {@link Item}s. 071 * 072 * @see IItemReuseStrategy 073 * 074 * @param strategy 075 * item reuse strategy 076 * @return this for chaining 077 */ 078 public AbstractTree<T> setItemReuseStrategy(IItemReuseStrategy strategy) 079 { 080 this.itemReuseStrategy = strategy; 081 082 return this; 083 } 084 085 /** 086 * @return currently set item reuse strategy. Defaults to <code>DefaultItemReuseStrategy</code> 087 * if none was set. 088 * 089 * @see DefaultItemReuseStrategy 090 */ 091 public IItemReuseStrategy getItemReuseStrategy() 092 { 093 if (itemReuseStrategy == null) 094 { 095 return DefaultItemReuseStrategy.getInstance(); 096 } 097 return itemReuseStrategy; 098 } 099 100 /** 101 * Get the provider of the tree nodes. 102 * 103 * @return provider 104 */ 105 public ITreeProvider<T> getProvider() 106 { 107 return provider; 108 } 109 110 /** 111 * Delegate to {@link #newModel()} if none is inited in super implementation. 112 */ 113 @Override 114 protected IModel<?> initModel() 115 { 116 IModel<?> model = super.initModel(); 117 118 if (model == null) 119 { 120 model = newModel(); 121 } 122 123 return model; 124 } 125 126 /** 127 * Factory method for a model, by default creates a model containing a {@link ProviderSubset}. 128 * Depending on your {@link ITreeProvider}'s model you might consider to provide a custom 129 * {@link Set} implementation. 130 * <p> 131 * Note: The contained {@link Set} has at least to implement {@link Set#add(Object)}, 132 * {@link Set#remove(Object)} and {@link Set#contains(Object)}. 133 * 134 * @return model for this tree 135 */ 136 protected IModel<Set<T>> newModel() 137 { 138 return new ProviderSubset<>(provider).createModel(); 139 } 140 141 /** 142 * Expand the given node, tries to update the affected branch if the change happens on an 143 * {@link AjaxRequestTarget}. 144 * 145 * @param t 146 * the node to expand 147 * 148 * @see #getModelObject() 149 * @see Set#add(Object) 150 * @see #updateBranch(Object, IPartialPageRequestHandler) 151 */ 152 public void expand(T t) 153 { 154 modelChanging(); 155 getModelObject().add(t); 156 modelChanged(); 157 158 getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent( 159 target -> updateBranch(t, target) 160 ); 161 } 162 163 /** 164 * Collapse the given node, tries to update the affected branch if the change happens on an 165 * {@link AjaxRequestTarget}. 166 * 167 * @param t 168 * the object to collapse 169 * 170 * @see #getModelObject() 171 * @see Set#remove(Object) 172 * @see #updateBranch(Object, IPartialPageRequestHandler) 173 */ 174 public void collapse(T t) 175 { 176 modelChanging(); 177 getModelObject().remove(t); 178 modelChanged(); 179 180 getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent( 181 target -> updateBranch(t, target) 182 ); 183 } 184 185 /** 186 * Get the given node's {@link State}. 187 * 188 * @param t 189 * the node to get state for 190 * @return state 191 * 192 * @see #getModelObject() 193 * @see Set#contains(Object) 194 */ 195 public State getState(T t) 196 { 197 if (getModelObject().contains(t)) 198 { 199 return State.EXPANDED; 200 } 201 else 202 { 203 return State.COLLAPSED; 204 } 205 } 206 207 /** 208 * Overridden to detach the {@link ITreeProvider}. 209 */ 210 @Override 211 protected void onDetach() 212 { 213 provider.detach(); 214 215 super.onDetach(); 216 } 217 218 /** 219 * Create a new component for a node. 220 * 221 * @param id 222 * the component id 223 * @param model 224 * the model containing the node 225 * @return created component 226 */ 227 public Component newNodeComponent(String id, final IModel<T> model) 228 { 229 return new Node<T>(id, this, model) 230 { 231 private static final long serialVersionUID = 1L; 232 233 @Override 234 protected Component createContent(String id, IModel<T> model) 235 { 236 return AbstractTree.this.newContentComponent(id, model); 237 } 238 }; 239 } 240 241 /** 242 * Create a new component for the content of a node. 243 * 244 * @param id 245 * the component id 246 * @param model 247 * the model containing the node 248 * @return created component 249 */ 250 protected abstract Component newContentComponent(String id, IModel<T> model); 251 252 /** 253 * Convenience method to update a single branch on an {@link AjaxRequestTarget}. Does nothing if 254 * the given node is currently not visible. 255 * 256 * @param node 257 * node to update 258 * @param target 259 * request target must not be @code null} 260 */ 261 public abstract void updateBranch(T node, IPartialPageRequestHandler target); 262 263 /** 264 * Convenience method to update a single node on an {@link AjaxRequestTarget}. Does nothing if 265 * the given node is currently not visible. 266 * 267 * @param node 268 * node to update 269 * @param target 270 * request target must not be @code null} 271 */ 272 public abstract void updateNode(T node, IPartialPageRequestHandler target); 273 274 /** 275 * The state of a node. 276 */ 277 public enum State { 278 /** 279 * The node is collapsed, i.e. its children are not iterated. 280 */ 281 COLLAPSED, 282 /** 283 * The node is expanded, i.e. its children are iterated. 284 */ 285 EXPANDED 286 } 287}