/src/wt/src/Wt/WFileUpload.h
Line | Count | Source |
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 | | */ |
130 | 0 | 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 | | */ |
138 | 0 | 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 | | */ |
188 | | const std::vector<Http::UploadedFile>& uploadedFiles() const |
189 | 0 | { 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 | | */ |
197 | 0 | 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 | | */ |
232 | 0 | 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 | | */ |
301 | 0 | 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 | | */ |
311 | 0 | 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_ |