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}