1 // This may look like C code, but it's really -*- C++ -*- 2 /* 3 * Copyright (C) 2008 Emweb bv, Herent, Belgium. 4 * 5 * See the LICENSE file for terms of use. 6 */ 7 #ifndef WFILEUPLOAD_H_ 8 #define WFILEUPLOAD_H_ 9 10 #include <Wt/WJavaScriptSlot.h> 11 #include <Wt/WWebWidget.h> 12 13 namespace Wt { 14 15 /*! \class WFileUpload Wt/WFileUpload.h Wt/WFileUpload.h 16 * \brief A widget that allows a file to be uploaded. 17 * 18 * This widget is displayed as a box in which a filename can be 19 * entered and a browse button. 20 * 21 * Depending on availability of JavaScript, the behaviour of the widget 22 * is different, but the API is designed in a way which facilitates 23 * a portable use. 24 * 25 * When JavaScript is available, the file will not be uploaded until 26 * upload() is called. This will start an asynchronous upload (and 27 * thus return immediately). \if cpp While the file is being uploaded, 28 * the dataReceived() signal is emitted when data is being received 29 * and if the connector library provides support (see also 30 * WResource::setUploadProgress() for a more detailed 31 * discussion). Although you can modify the GUI from this signal, you 32 * still need to have a mechanism in place to update the client 33 * interface (using a WTimer or using \link 34 * WApplication::enableUpdates() server-push\endlink). When the file 35 * has been uploaded, the uploaded() signal is emitted, or if the file 36 * was too large, the fileTooLarge() signal is emitted. You may 37 * configure a progress bar that is used to show the upload progress 38 * using setProgressBar(). \endif 39 * 40 * When no JavaScript is available, the file will be uploaded with the 41 * next click event. Thus, upload() has no effect -- the file will 42 * already be uploaded, and the corresponding signals will already be 43 * emitted. To test if upload() will start an upload, you may check 44 * using the canUpload() call. 45 * 46 * Thus, to properly use the widget, one needs to follow these 47 * rules: 48 * <ul> 49 * <li>Be prepared to handle the uploaded() or fileTooLarge() signals 50 * also when upload() was not called.</li> 51 * <li>Check using canUpload() if upload() will schedule a new 52 * upload. if (!canUpload()) then upload() will not have any 53 * effect. if (canUpload()), upload() will start a new file upload, 54 * which completes succesfully using an uploaded() signal or a 55 * fileTooLarge() signals gets emitted. 56 * </li> 57 * </ul> 58 * 59 * The %WFileUpload widget must be hidden or deleted when a file is 60 * received. In addition it is wise to prevent the user from uploading 61 * the file twice as in the example below. 62 * 63 * The uploaded file is automatically spooled to a local temporary 64 * file which will be deleted together with the WFileUpload widget, 65 * unless stealSpooledFile() is called. 66 * 67 * \if cpp 68 * Usage example: 69 * \code 70 * Wt::WFileUpload *upload = addWidget(std::make_unique<Wt::WFileUpload>()); 71 * upload->setFileTextSize(40); 72 * 73 * // Provide a button 74 * Wt::WPushButton *uploadButton = 75 * addWidget(std::make_unique<Wt::WPushButton>("Send")); 76 77 * // Upload when the button is clicked. 78 * uploadButton->clicked().connect(upload, &Wt::WFileUpload::upload); 79 * uploadButton->clicked().connect(uploadButton, &Wt::WPushButton::disable); 80 * 81 * // Upload automatically when the user entered a file. 82 * upload->changed().connect(upload, &WFileUpload::upload); 83 * upload->changed().connect(uploadButton, &Wt::WPushButton::disable); 84 * 85 * // React to a succesfull upload. 86 * upload->uploaded().connect(this, &MyWidget::fileUploaded); 87 * 88 * // React to a fileupload problem. 89 * upload->fileTooLarge().connect(this, &MyWidget::fileTooLarge); 90 * \endcode 91 * \endif 92 * 93 * %WFileUpload is an \link WWidget::setInline(bool) inline \endlink widget. 94 * 95 * <h3>CSS</h3> 96 * 97 * The file upload itself corresponds to a <tt><input 98 * type="file"></tt> tag, but may be wrapped in a 99 * <tt><form></tt> tag for an Ajax session to implement the 100 * asynchronous upload action. This widget does not provide styling, 101 * and styling through CSS is not well supported across browsers. 102 */ 103 class WT_API WFileUpload : public WWebWidget 104 { 105 public: 106 /*! \brief Creates a file upload widget 107 */ 108 WFileUpload(); 109 110 ~WFileUpload(); 111 112 /*! \brief Sets whether the file upload accepts multiple files. 113 * 114 * In browsers which support the "multiple" attribute for the file 115 * upload (to be part of HTML5) control, this will allow the user to 116 * select multiple files at once. 117 * 118 * All uploaded files are available from uploadedFiles(). The 119 * single-file API will return only information on the first 120 * uploaded file. 121 * 122 * The default value is \c false. 123 */ 124 void setMultiple(bool multiple); 125 126 /*! \brief Returns whether multiple files can be uploaded. 127 * 128 * \sa setMultiple() 129 */ multiple()130 bool multiple() const { return flags_.test(BIT_MULTIPLE); } 131 132 /*! \brief Sets the size of the file input. 133 */ 134 void setFileTextSize(int chars); 135 136 /*! \brief Returns the size of the file input. 137 */ fileTextSize()138 int fileTextSize() const { return textSize_; } 139 140 /*! \brief Returns the spooled location of the uploaded file. 141 * 142 * Returns the temporary filename in which the uploaded file was 143 * spooled. The file is guaranteed to exist as long as the 144 * WFileUpload widget is not deleted, or a new file is not uploaded. 145 * 146 * When multiple files were uploaded, this returns the information 147 * from the first file. 148 * 149 * \sa stealSpooledFile() 150 * \sa uploaded 151 */ 152 std::string spoolFileName() const; 153 154 /*! \brief Returns the client filename. 155 * 156 * When multiple files were uploaded, this returns the information 157 * from the first file. 158 * 159 * \note Depending on the browser this is an absolute path 160 * or only the file name. 161 */ 162 WT_USTRING clientFileName() const; 163 164 /*! \brief Returns the client content description. 165 * 166 * When multiple files were uploaded, this returns the information 167 * from the first file. 168 */ 169 WT_USTRING contentDescription() const; 170 171 /*! \brief Steals the spooled file. 172 * 173 * By stealing the file, the spooled file will no longer be deleted 174 * together with this widget, which means you need to take care of 175 * managing that. 176 * 177 * When multiple files were uploaded, this returns the information 178 * from the first file. 179 */ 180 void stealSpooledFile(); 181 182 /*! \brief Returns whether one or more files have been uploaded. 183 */ 184 bool empty() const; 185 186 /*! \brief Returns the uploaded files. 187 */ uploadedFiles()188 const std::vector<Http::UploadedFile>& uploadedFiles() const 189 { return uploadedFiles_; } 190 191 /*! \brief Returns whether upload() will start a new file upload. 192 * 193 * A call to upload() will only start a new file upload if there is 194 * no JavaScript support. Otherwise, the most recent file will 195 * already be uploaded. 196 */ canUpload()197 bool canUpload() const { return fileUploadTarget_ != nullptr; } 198 199 /*! \brief Use the click signal of another widget to open the file picker. 200 * 201 * This hides the default WFileUpload widget and uses the click-signal of the argument to 202 * open the file picker. The upload logic is still handled by WFileUpload behind the scenes. 203 * This action cannot be undone. 204 * 205 * WFileUpload does not take ownership of the widget, nor does it display it. You must still 206 * place it in the widget tree yourself. 207 */ 208 void setDisplayWidget(WInteractWidget *widget); 209 210 /*! \brief %Signal emitted when a new file was uploaded. 211 * 212 * This signal is emitted when file upload has been completed. It 213 * is good practice to hide or delete the WFileUpload widget when a 214 * file has been uploaded succesfully. 215 * 216 * \sa upload() 217 * \sa fileTooLarge() 218 */ 219 EventSignal<>& uploaded(); 220 221 /*! \brief %Signal emitted when the user tried to upload a too large file. 222 * 223 * The parameter is the (approximate) size of the file (in bytes) the user 224 * tried to upload. 225 * 226 * The maximum file size is determined by the maximum request size, 227 * which may be configured in the configuration file (<max-request-size>). 228 * 229 * \sa uploaded() 230 * \sa WApplication::requestTooLarge() 231 */ fileTooLarge()232 JSignal< ::int64_t>& fileTooLarge() { return fileTooLarge_; } 233 234 /*! \brief %Signal emitted when the user selected a new file. 235 * 236 * One could react on the user selecting a (new) file, by uploading 237 * the file immediately. 238 * 239 * Caveat: this signal is not emitted with konqueror and possibly 240 * other browsers. Thus, in the above scenario you should still provide 241 * an alternative way to call the upload() method. 242 */ 243 EventSignal<>& changed(); 244 245 /*! \brief Starts the file upload. 246 * 247 * The uploaded() signal is emitted when a file is uploaded, or the 248 * fileTooLarge() signal is emitted when the file size exceeded the 249 * maximum request size. 250 * 251 * \sa uploaded() 252 * \sa canUpload() 253 */ 254 void upload(); 255 256 /*! \brief Sets a progress bar to indicate upload progress. 257 * 258 * When the file is being uploaded, upload progress is indicated 259 * using the provided progress bar. Both the progress bar range and 260 * values are configured when the upload starts. 261 * 262 * The file upload itself is hidden as soon as the upload starts. 263 * 264 * The default progress bar is 0 (no upload progress is indicated). 265 * 266 * \if java 267 * To update the progess bar server push is used, you should only 268 * use this functionality when using a Servlet 3.0 compatible servlet 269 * container. 270 * \endif 271 * 272 * \sa dataReceived() 273 */ 274 void setProgressBar(WProgressBar *progressBar); 275 276 /*! \brief Sets a progress bar to indicate upload progress. 277 * 278 * When the file is being uploaded, upload progress is indicated 279 * using the provided progress bar. Both the progress bar range and 280 * values are configured when the upload starts. 281 * 282 * The bar becomes part of the file upload, and replaces the file 283 * prompt when the upload is started. 284 * 285 * The default progress bar is 0 (no upload progress is indicated). 286 * 287 * \if java 288 * To update the progess bar server push is used, you should only 289 * use this functionality when using a Servlet 3.0 compatible servlet 290 * container. 291 * \endif 292 * 293 * \sa dataReceived() 294 */ 295 void setProgressBar(std::unique_ptr<WProgressBar> progressBar); 296 297 /*! \brief Returns the progress bar. 298 * 299 * \sa setProgressBar() 300 */ progressBar()301 WProgressBar *progressBar() const { return progressBar_; } 302 303 /*! \brief %Signal emitted while a file is being uploaded. 304 * 305 * When supported by the connector library, you can track the 306 * progress of the file upload by listening to this signal. 307 * 308 * The first argument is the number of bytes received so far, 309 * and the second argument is the total number of bytes. 310 */ dataReceived()311 Signal< ::uint64_t, ::uint64_t>& dataReceived() { return dataReceived_; } 312 313 virtual void enableAjax() override; 314 315 /// 316 /// \brief Sets input accept attributes 317 /// 318 /// The accept attribute may be specified to provide user agents with a hint 319 /// of what file types will be accepted. 320 /// Use html input accept attributes as input. 321 /// 322 /// \code 323 /// WFileUpload *fu = new WFileUpload(root()); 324 /// fu->setFilters("image/*"); 325 /// \endcode 326 /// 327 void setFilters(const std::string& acceptAttributes); 328 329 private: 330 static const char *CHANGE_SIGNAL; 331 static const char *UPLOADED_SIGNAL; 332 333 static const int BIT_DO_UPLOAD = 0; 334 static const int BIT_ENABLE_AJAX = 1; 335 static const int BIT_UPLOADING = 2; 336 static const int BIT_MULTIPLE = 3; 337 static const int BIT_ENABLED_CHANGED = 4; 338 static const int BIT_ACCEPT_ATTRIBUTE_CHANGED = 5; 339 static const int BIT_USE_DISPLAY_WIDGET = 6; 340 341 std::bitset<7> flags_; 342 343 int textSize_; 344 345 std::vector<Http::UploadedFile> uploadedFiles_; 346 347 JSignal< ::int64_t> fileTooLarge_; 348 349 Signal< ::uint64_t, ::uint64_t> dataReceived_; 350 351 Core::observing_ptr<WInteractWidget> displayWidget_; 352 JSlot displayWidgetRedirect_; 353 354 std::unique_ptr<WResource> fileUploadTarget_; 355 std::unique_ptr<WProgressBar> containedProgressBar_; 356 WProgressBar *progressBar_; 357 358 std::string acceptAttributes_; 359 void create(); 360 361 void onData(::uint64_t current, ::uint64_t total); 362 void onDataExceeded(::uint64_t dataExceeded); 363 364 std::string displayWidgetClickJS(); 365 366 virtual void setRequestTooLarge(::int64_t size) override; 367 368 protected: 369 virtual void updateDom(DomElement& element, bool all) override; 370 virtual DomElement *createDomElement(WApplication *app) override; 371 virtual DomElementType domElementType() const override; 372 virtual void propagateRenderOk(bool deep) override; 373 virtual void getDomChanges(std::vector<DomElement *>& result, 374 WApplication *app) override; 375 virtual void propagateSetEnabled(bool enabled) override; 376 virtual std::string renderRemoveJs(bool recursive) override; 377 378 private: 379 void handleFileTooLarge(::int64_t fileSize); 380 381 void onUploaded(); 382 383 virtual void setFormData(const FormData& formData) override; 384 void setFiles(const std::vector<Http::UploadedFile>& files); 385 386 friend class WFileUploadResource; 387 }; 388 389 } 390 391 #endif // WFILEUPLOAD_H_ 392