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.Optional; 020 021import org.apache.wicket.Component; 022import org.apache.wicket.MarkupContainer; 023import org.apache.wicket.ajax.AjaxRequestTarget; 024import org.apache.wicket.ajax.markup.html.AjaxFallbackLink; 025import org.apache.wicket.behavior.Behavior; 026import org.apache.wicket.core.util.string.CssUtils; 027import org.apache.wicket.extensions.markup.html.repeater.tree.AbstractTree.State; 028import org.apache.wicket.markup.ComponentTag; 029import org.apache.wicket.markup.html.panel.Panel; 030import org.apache.wicket.model.IModel; 031 032/** 033 * Representation of a single node in the tree. By default uses an {@link AjaxFallbackLink} for its 034 * junction component. 035 * 036 * @see #createJunctionComponent(String) 037 * 038 * @author svenmeier 039 * @param <T> 040 * The model object type 041 */ 042public abstract class Node<T> extends Panel 043{ 044 045 private static final long serialVersionUID = 1L; 046 047 public static final String OTHER_CLASS_KEY = CssUtils.key(Node.class, "other"); 048 049 public static final String EXPANDED_CLASS_KEY = CssUtils.key(Node.class, "expanded"); 050 051 public static final String COLLAPSED_CLASS_KEY = CssUtils.key(Node.class, "collapsed"); 052 053 /** 054 * The component id for the content component. 055 */ 056 public static final String CONTENT_ID = "content"; 057 058 private AbstractTree<T> tree; 059 060 /** 061 * Constructor. 062 * 063 * @param id 064 * component id 065 * @param tree 066 * the owning tree 067 * @param model 068 * the model for this node 069 */ 070 public Node(String id, AbstractTree<T> tree, IModel<T> model) 071 { 072 super(id, model); 073 074 this.tree = tree; 075 } 076 077 @Override 078 protected void onInitialize() 079 { 080 super.onInitialize(); 081 082 MarkupContainer junction = createJunctionComponent("junction"); 083 junction.add(new StyleBehavior()); 084 add(junction); 085 086 Component content = createContent(CONTENT_ID, getModel()); 087 if (!content.getId().equals(CONTENT_ID)) 088 { 089 throw new IllegalArgumentException( 090 "content must have component id equal to Node.CONTENT_ID"); 091 } 092 add(content); 093 } 094 095 /** 096 * @return the model 097 */ 098 @SuppressWarnings("unchecked") 099 public IModel<T> getModel() 100 { 101 return (IModel<T>)getDefaultModel(); 102 } 103 104 /** 105 * @return the model object 106 */ 107 public T getModelObject() 108 { 109 return getModel().getObject(); 110 } 111 112 /** 113 * The junction component expands and collapses this node. 114 * 115 * @param id 116 * the component id 117 * @return component representing the junction 118 */ 119 protected MarkupContainer createJunctionComponent(String id) 120 { 121 return new AjaxFallbackLink<Void>(id) 122 { 123 private static final long serialVersionUID = 1L; 124 125 @Override 126 public void onClick(Optional<AjaxRequestTarget> targetOptional) 127 { 128 toggle(); 129 } 130 131 @Override 132 public boolean isEnabled() 133 { 134 return tree.getProvider().hasChildren(Node.this.getModelObject()); 135 } 136 }; 137 } 138 139 /** 140 * Toggle the node. 141 * 142 * @see AbstractTree#collapse(Object) 143 * @see AbstractTree#expand(Object) 144 */ 145 protected void toggle() 146 { 147 T t = getModelObject(); 148 149 if (tree.getState(t) == State.EXPANDED) 150 { 151 tree.collapse(t); 152 } 153 else 154 { 155 tree.expand(t); 156 } 157 } 158 159 /** 160 * Create the component to display the actual node's content. 161 * 162 * @param id 163 * the component id 164 * @param model 165 * the node's model 166 * @return the component representing the content 167 */ 168 protected abstract Component createContent(String id, IModel<T> model); 169 170 /** 171 * Get the style class depending on the current {@link State} of this node. 172 * 173 * @see #getExpandedStyleClass(Object) 174 * @see #getCollapsedStyleClass() 175 * @see #getOtherStyleClass() 176 * @return the style class 177 */ 178 protected String getStyleClass() 179 { 180 T t = getModelObject(); 181 182 if (tree.getProvider().hasChildren(t)) 183 { 184 if (tree.getState(t) == State.EXPANDED) 185 { 186 return getExpandedStyleClass(t); 187 } 188 else 189 { 190 return getCollapsedStyleClass(); 191 } 192 } 193 return getOtherStyleClass(); 194 } 195 196 protected String getExpandedStyleClass(T t) 197 { 198 return getString(EXPANDED_CLASS_KEY); 199 } 200 201 protected String getCollapsedStyleClass() 202 { 203 return getString(COLLAPSED_CLASS_KEY); 204 } 205 206 protected String getOtherStyleClass() 207 { 208 return getString(OTHER_CLASS_KEY); 209 } 210 211 /** 212 * Behavior to add the style class attribute. 213 * 214 * @see Node#getStyleClass() 215 */ 216 private static class StyleBehavior extends Behavior 217 { 218 private static final long serialVersionUID = 1L; 219 220 @Override 221 public void onComponentTag(Component component, ComponentTag tag) 222 { 223 Node<?> node = (Node<?>)component.getParent(); 224 225 String styleClass = node.getStyleClass(); 226 if (styleClass != null) 227 { 228 tag.put("class", styleClass); 229 } 230 } 231 } 232}