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