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.ajax.markup.html.form.upload;
018
019import java.util.Formatter;
020
021import org.apache.wicket.Application;
022import org.apache.wicket.IInitializer;
023import org.apache.wicket.MarkupContainer;
024import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
025import org.apache.wicket.markup.head.CssHeaderItem;
026import org.apache.wicket.markup.head.IHeaderResponse;
027import org.apache.wicket.markup.head.JavaScriptHeaderItem;
028import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
029import org.apache.wicket.markup.html.WebMarkupContainer;
030import org.apache.wicket.markup.html.form.Form;
031import org.apache.wicket.markup.html.form.upload.FileUploadField;
032import org.apache.wicket.markup.html.panel.Panel;
033import org.apache.wicket.model.StringResourceModel;
034import org.apache.wicket.request.resource.CssResourceReference;
035import org.apache.wicket.request.resource.JavaScriptResourceReference;
036import org.apache.wicket.request.resource.ResourceReference;
037import org.apache.wicket.request.resource.SharedResourceReference;
038import org.apache.wicket.resource.CoreLibrariesContributor;
039import org.apache.wicket.util.lang.Args;
040
041/**
042 * A panel to show the progress of an HTTP upload.
043 * <p>
044 * Note: For this to work upload progress monitoring must be enabled in the wicket application.
045 * Example:
046 * 
047 * <pre>
048 * <code>
049 *  public class App extends WebApplication {
050 * 
051 *      &#64;Override
052 *      protected void init() {
053 *              super.init();
054 * 
055 *              &lt;b&gt;getApplicationSettings().setUploadProgressUpdatesEnabled(true);&lt;/b&gt; // &lt;--
056 *      }
057 * }
058 * </code>
059 * </pre>
060 * 
061 * For customizing starting text see {@link #RESOURCE_STARTING}.
062 * 
063 * Implementation detail: Despite being located in an Ajax package, the progress communication is
064 * not done via Ajax but with an IFrame instead due to a bug in Webkit based browsers, see
065 * WICKET-3202.
066 * 
067 * @author Andrew Lombardi
068 */
069public class UploadProgressBar extends Panel
070{
071        /**
072         * Resource key used to retrieve starting message for.
073         * 
074         * Example: UploadProgressBar.starting=Upload starting...
075         */
076        public static final String RESOURCE_STARTING = "UploadProgressBar.starting";
077
078        /**
079         * Initializer for this component; binds static resources.
080         */
081        public final static class ComponentInitializer implements IInitializer
082        {
083                @Override
084                public void init(final Application application)
085                {
086                        // register the upload status resource
087                        application.getSharedResources().add(RESOURCE_NAME, new UploadStatusResource());
088                }
089
090                @Override
091                public String toString()
092                {
093                        return "UploadProgressBar initializer";
094                }
095
096                @Override
097                public void destroy(final Application application)
098                {
099                }
100        }
101
102        private static final ResourceReference JS = new JavaScriptResourceReference(
103                UploadProgressBar.class, "progressbar.js");
104
105        private static final ResourceReference CSS = new CssResourceReference(
106                UploadProgressBar.class, "UploadProgressBar.css");
107
108        private static final String RESOURCE_NAME = UploadProgressBar.class.getName();
109
110        private static final long serialVersionUID = 1L;
111
112        private Form<?> form;
113
114        private MarkupContainer statusDiv;
115
116        private MarkupContainer barDiv;
117
118        private final FileUploadField uploadField;
119
120        /**
121         * Constructor that will display the upload progress bar for every submit of the given form.
122         *
123         * @param id
124         *            component id (not null)
125         * @param uploadField
126         *            the file upload field to check for a file upload, or null to display the upload
127         *            field for every submit of the given form
128         */
129        public UploadProgressBar(final String id,  final FileUploadField uploadField)
130        {
131                super(id);
132
133                this.uploadField = uploadField;
134                if (uploadField != null)
135                {
136                        uploadField.setOutputMarkupId(true);
137                }
138
139                setRenderBodyOnly(true);
140        }
141
142
143
144        /**
145         * Constructor that will display the upload progress bar for every submit of the given form.
146         * 
147         * @param id
148         *            component id (not null)
149         * @param form
150         *            form that will be submitted (not null)
151         */
152        public UploadProgressBar(final String id, final Form<?> form)
153        {
154                this(id, form, null);
155        }
156
157        /**
158         * Constructor that will display the upload progress bar for submissions of the given form, that
159         * include a file upload in the given file upload field; i.e. if the user did not select a file
160         * in the given file upload field, the progess bar is not displayed.
161         * 
162         * @param id
163         *            component id (not null)
164         * @param form
165         *            form that is submitted (not null)
166         * @param uploadField
167         *            the file upload field to check for a file upload, or null to display the upload
168         *            field for every submit of the given form
169         */
170        public UploadProgressBar(final String id, final Form<?> form, final FileUploadField uploadField)
171        {
172                super(id);
173
174                this.uploadField = uploadField;
175                if (uploadField != null)
176                {
177                        uploadField.setOutputMarkupId(true);
178                }
179
180                this.form = Args.notNull(form, "form");
181                form.setOutputMarkupId(true);
182
183                setRenderBodyOnly(true);
184        }
185
186        @Override
187        protected void onInitialize()
188        {
189                super.onInitialize();
190                Form<?> form = getCallbackForm();
191                if (form != null) {
192                        form.setOutputMarkupId(true);
193                }
194
195                barDiv = newBarComponent("bar");
196                add(barDiv);
197
198                statusDiv = newStatusComponent("status");
199                add(statusDiv);
200        }
201
202        /**
203         * Creates a component for the status text
204         *
205         * @param id
206         *          The component id
207         * @return the status component
208         */
209        protected MarkupContainer newStatusComponent(String id)
210        {
211                WebMarkupContainer status = new WebMarkupContainer(id);
212                status.setOutputMarkupId(true);
213                return status;
214        }
215
216        /**
217         * Creates a component for the bar
218         *
219         * @param id
220         *          The component id
221         * @return the bar component
222         */
223        protected MarkupContainer newBarComponent(String id)
224        {
225                WebMarkupContainer bar = new WebMarkupContainer(id);
226                bar.setOutputMarkupId(true);
227                return bar;
228        }
229
230        /**
231         * Override this to provide your own CSS, or return null to avoid including the default.
232         * 
233         * @return ResourceReference for your CSS.
234         */
235        protected ResourceReference getCss()
236        {
237                return CSS;
238        }
239
240        /**
241         * {@inheritDoc}
242         */
243        @Override
244        public void renderHead(final IHeaderResponse response)
245        {
246                super.renderHead(response);
247
248                CoreLibrariesContributor.contributeAjax(getApplication(), response);
249                response.render(JavaScriptHeaderItem.forReference(JS));
250                ResourceReference css = getCss();
251                if (css != null)
252                {
253                        response.render(CssHeaderItem.forReference(css));
254                }
255
256                ResourceReference ref = new SharedResourceReference(RESOURCE_NAME);
257
258                final String uploadFieldId = (uploadField == null) ? "null" : ("'" + uploadField.getMarkupId() + "'");
259
260                final String status = new StringResourceModel(RESOURCE_STARTING, this, null).getString();
261
262                CharSequence url = form != null ? urlFor(ref, UploadStatusResource.newParameter(getPage().getId())) :
263                                urlFor(ref, UploadStatusResource.newParameter(uploadField.getMarkupId()));
264
265                StringBuilder builder = new StringBuilder(128);
266                Formatter formatter = new Formatter(builder);
267
268                Form<?> form = getCallbackForm();
269
270                formatter.format(getVarName() + " = new Wicket.WUPB(%s, '%s', '%s', '%s', %s, '%s', %s);",
271                                form != null ? "'" + form.getMarkupId() + "'" : "null", statusDiv.getMarkupId(), barDiv.getMarkupId(), url, uploadFieldId,
272                        status, getOnProgressUpdatedCallBack());
273                response.render(OnDomReadyHeaderItem.forScript(builder.toString()));
274        }
275
276        /**
277         * Allows to pass a JavaScript function that is called when progress in updated.
278         *
279         * @return A JavaScript function.
280         */
281        protected String getOnProgressUpdatedCallBack()
282        {
283                return "function(percent) {}";
284        }
285
286        private String getVarName() {
287                return "window.upb_" + barDiv.getMarkupId();
288        }
289
290
291        public void start(IPartialPageRequestHandler handler)
292        {
293                handler.appendJavaScript(getVarName() + ".start();");
294        }
295
296        /**
297         * Form on where will be installed the JavaScript callback to present the progress bar.
298         * 
299         * @return form
300         */
301        private Form<?> getCallbackForm()
302        {
303                if (form == null)
304                {
305                        return null;
306                }
307                return form.getRootForm();
308        }
309}