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.devutils.debugbar;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.apache.wicket.Application;
023import org.apache.wicket.AttributeModifier;
024import org.apache.wicket.Component;
025import org.apache.wicket.MetaDataKey;
026import org.apache.wicket.devutils.DevUtilsPanel;
027import org.apache.wicket.markup.head.CssHeaderItem;
028import org.apache.wicket.markup.head.IHeaderResponse;
029import org.apache.wicket.markup.head.JavaScriptHeaderItem;
030import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
031import org.apache.wicket.markup.html.WebMarkupContainer;
032import org.apache.wicket.markup.html.image.Image;
033import org.apache.wicket.markup.html.list.ListItem;
034import org.apache.wicket.markup.html.list.ListView;
035import org.apache.wicket.model.IModel;
036import org.apache.wicket.request.resource.CssResourceReference;
037import org.apache.wicket.request.resource.JavaScriptResourceReference;
038import org.apache.wicket.request.resource.PackageResourceReference;
039import org.apache.wicket.resource.CoreLibrariesContributor;
040import org.apache.wicket.util.lang.Args;
041
042/**
043 * The debug bar is for use during development. It allows contributors to add useful functions or
044 * data, making them readily accessible to the developer.<br />
045 * <br />
046 * To use it, simply add it to your base page so that all of your pages automatically have it.<br />
047 * 
048 * <br />
049 * <code>
050 * Java:
051 * add(new DebugBar("debug"));
052 * 
053 * HTML:
054 * &lt;div wicket:id="debug"&gt;&lt;/div&gt;
055 * </code>
056 * 
057 * <br />
058 * You can also add your own information to the bar by creating a {@link IDebugBarContributor} and
059 * registering it with the debug bar.
060 *
061 * <p>The debug bar uses CSS absolute positioning to appear in the top-right corner of the page.
062 * <strong>Important</strong>: if there is an element with a z-index in this part of your page, the DebugBar will need a higher
063 * "z-index" style value to show up. Or you can use different position for it. See wicket-debugbar.css.</p>
064 *
065 * @author Jeremy Thomerson
066 * @see IDebugBarContributor
067 */
068public class DebugBar extends DevUtilsPanel
069{
070        private static final long serialVersionUID = 1L;
071
072        private static final MetaDataKey<List<IDebugBarContributor>> CONTRIBS_META_KEY = new MetaDataKey<>()
073        {
074                private static final long serialVersionUID = 1L;
075        };
076
077        
078        /**
079         * Construct.
080         * <p/>
081         * Create debug bar (initially expanded)
082         * 
083         * @param id
084         *         component id
085         *         
086         * @see #DebugBar(String, boolean) 
087         */
088        public DebugBar(final String id)
089        {
090                this(id, true);
091        }
092        
093        /**
094         * Construct.
095         * 
096         * @param id
097         *         component id
098         * @param initiallyExpanded
099         *         {@code true} to show debug bar initially expanded
100         *         
101         * @see #DebugBar(String) 
102         */
103        public DebugBar(final String id, boolean initiallyExpanded)
104        {
105                super(id);
106                setMarkupId("wicketDebugBar");
107                setOutputMarkupId(true);
108                add(AttributeModifier.replace("class", new IModel<String>()
109                {
110                        private static final long serialVersionUID = 1L;
111
112                        @Override
113                        public String getObject()
114                        {
115                                return "wicketDebugBar" + (DebugBar.this.hasErrorMessage() ? "Error" : "");
116                        }
117                }));
118
119                add(new Image("logo", new PackageResourceReference(DebugBar.class, "wicket.png")));
120                
121                add(contentSection("content", initiallyExpanded));
122        }
123
124
125        /**
126         * Positions the DebugBar at the bottom of the page
127         * @return
128         */
129        public DebugBar positionBottom()
130        {
131                add(AttributeModifier.append("class", "bottom"));
132                return this;
133        }
134
135        private Component contentSection(final String id, boolean initiallyExpanded)
136        {
137                WebMarkupContainer section = new WebMarkupContainer(id);
138                if (!initiallyExpanded)
139                {
140                        section.add(AttributeModifier.append("style", "display:none").setSeparator(";"));
141                }
142
143                List<IDebugBarContributor> contributors = getContributors(getApplication());
144
145                section.add(new ListView<IDebugBarContributor>("contributors", contributors)
146                {
147                        private static final long serialVersionUID = 1L;
148
149                        @Override
150                        protected void populateItem(final ListItem<IDebugBarContributor> item)
151                        {
152                                IDebugBarContributor contrib = item.getModelObject();
153                                Component comp = contrib.createComponent("contrib", DebugBar.this);
154                                if (comp == null)
155                                {
156                                        // some contributors only add information to the debug bar
157                                        // and don't actually create a contributed component
158                                        item.setVisibilityAllowed(false);
159                                }
160                                else
161                                {
162                                        item.add(comp);
163                                }
164                        }
165                });
166
167                section.add(new Image("removeImg", new PackageResourceReference(DebugBar.class, "remove.png")));
168
169                return section;
170        }
171
172        @Override
173        public boolean isVisible()
174        {
175                return getApplication().getDebugSettings().isDevelopmentUtilitiesEnabled();
176        }
177
178        @Override
179        public void renderHead(final IHeaderResponse response)
180        {
181                CoreLibrariesContributor.contributeAjax(getApplication(), response);
182                
183                response.render(CssHeaderItem.forReference(new CssResourceReference(DebugBar.class,
184                        "wicket-debugbar.css")));
185                response.render(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(
186                        DebugBar.class, "wicket-debugbar.js")));
187                response.render(OnDomReadyHeaderItem.forScript("Wicket.debugBar();"));
188        }
189
190        /**
191         * Register your own custom contributor that will be part of the debug bar. You must have the
192         * context of an application for this thread at the time of calling this method.
193         * 
194         * @param contrib
195         *            custom contributor - can not be null
196         */
197        public static void registerContributor(final IDebugBarContributor contrib)
198        {
199                registerContributor(contrib, Application.get());
200        }
201
202        /**
203         * Register your own custom contributor that will be part of the debug bar. You must have the
204         * context of an application for this thread at the time of calling this method.
205         * 
206         * @param application
207         * @param contrib
208         *            custom contributor - can not be null
209         */
210        public static void registerContributor(final IDebugBarContributor contrib,
211                final Application application)
212        {
213                Args.notNull(contrib, "contrib");
214
215                List<IDebugBarContributor> contributors = getContributors(application);
216                contributors.add(contrib);
217                setContributors(contributors, application);
218        }
219
220        public static List<IDebugBarContributor> getContributors(final Application application)
221        {
222                List<IDebugBarContributor> list = application.getMetaData(CONTRIBS_META_KEY);
223                return list == null ? new ArrayList<IDebugBarContributor>() : list;
224        }
225
226        public static void setContributors(List<IDebugBarContributor> contributors, Application application)
227        {
228                Args.notNull(application, "application");
229
230                application.setMetaData(CONTRIBS_META_KEY, contributors);
231        }
232}