Merge "Split DateInputWidget & CalendarWidget into a separate ResourceLoader module"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.Upload.BookletLayout.js
1 ( function ( $, mw ) {
2
3 /**
4 * mw.Upload.BookletLayout encapsulates the process of uploading a file
5 * to MediaWiki using the {@link mw.Upload upload model}.
6 * The booklet emits events that can be used to get the stashed
7 * upload and the final file. It can be extended to accept
8 * additional fields from the user for specific scenarios like
9 * for Commons, or campaigns.
10 *
11 * ## Structure
12 *
13 * The {@link OO.ui.BookletLayout booklet layout} has three steps:
14 *
15 * - **Upload**: Has a {@link OO.ui.SelectFileWidget field} to get the file object.
16 *
17 * - **Information**: Has a {@link OO.ui.FormLayout form} to collect metadata. This can be
18 * extended.
19 *
20 * - **Insert**: Has details on how to use the file that was uploaded.
21 *
22 * Each step has a form associated with it defined in
23 * {@link #renderUploadForm renderUploadForm},
24 * {@link #renderInfoForm renderInfoForm}, and
25 * {@link #renderInsertForm renderInfoForm}. The
26 * {@link #getFile getFile},
27 * {@link #getFilename getFilename}, and
28 * {@link #getText getText} methods are used to get
29 * the information filled in these forms, required to call
30 * {@link mw.Upload mw.Upload}.
31 *
32 * ## Usage
33 *
34 * See the {@link mw.Upload.Dialog upload dialog}.
35 *
36 * The {@link #event-fileUploaded fileUploaded},
37 * and {@link #event-fileSaved fileSaved} events can
38 * be used to get details of the upload.
39 *
40 * ## Extending
41 *
42 * To extend using {@link mw.Upload mw.Upload}, override
43 * {@link #renderInfoForm renderInfoForm} to render
44 * the form required for the specific use-case. Update the
45 * {@link #getFilename getFilename}, and
46 * {@link #getText getText} methods to return data
47 * from your newly created form. If you added new fields you'll also have
48 * to update the {@link #clear} method.
49 *
50 * If you plan to use a different upload model, apart from what is mentioned
51 * above, you'll also have to override the
52 * {@link #createUpload createUpload} method to
53 * return the new model. The {@link #saveFile saveFile}, and
54 * the {@link #uploadFile uploadFile} methods need to be
55 * overriden to use the new model and data returned from the forms.
56 *
57 * @class
58 * @extends OO.ui.BookletLayout
59 *
60 * @constructor
61 * @param {Object} config Configuration options
62 * @cfg {jQuery} [$overlay] Overlay to use for widgets in the booklet
63 */
64 mw.Upload.BookletLayout = function ( config ) {
65 // Parent constructor
66 mw.Upload.BookletLayout.parent.call( this, config );
67
68 this.$overlay = config.$overlay;
69
70 this.renderUploadForm();
71 this.renderInfoForm();
72 this.renderInsertForm();
73
74 this.addPages( [
75 new OO.ui.PageLayout( 'upload', {
76 scrollable: true,
77 padded: true,
78 content: [ this.uploadForm ]
79 } ),
80 new OO.ui.PageLayout( 'info', {
81 scrollable: true,
82 padded: true,
83 content: [ this.infoForm ]
84 } ),
85 new OO.ui.PageLayout( 'insert', {
86 scrollable: true,
87 padded: true,
88 content: [ this.insertForm ]
89 } )
90 ] );
91 };
92
93 /* Setup */
94
95 OO.inheritClass( mw.Upload.BookletLayout, OO.ui.BookletLayout );
96
97 /* Events */
98
99 /**
100 * The file has finished uploading
101 *
102 * @event fileUploaded
103 */
104
105 /**
106 * The file has been saved to the database
107 *
108 * @event fileSaved
109 */
110
111 /**
112 * The upload form has changed
113 *
114 * @event uploadValid
115 * @param {boolean} isValid The form is valid
116 */
117
118 /**
119 * The info form has changed
120 *
121 * @event infoValid
122 * @param {boolean} isValid The form is valid
123 */
124
125 /* Properties */
126
127 /**
128 * @property {OO.ui.FormLayout} uploadForm
129 * The form rendered in the first step to get the file object.
130 * Rendered in {@link #renderUploadForm renderUploadForm}.
131 */
132
133 /**
134 * @property {OO.ui.FormLayout} infoForm
135 * The form rendered in the second step to get metadata.
136 * Rendered in {@link #renderInfoForm renderInfoForm}
137 */
138
139 /**
140 * @property {OO.ui.FormLayout} insertForm
141 * The form rendered in the third step to show usage
142 * Rendered in {@link #renderInsertForm renderInsertForm}
143 */
144
145 /* Methods */
146
147 /**
148 * Initialize for a new upload
149 */
150 mw.Upload.BookletLayout.prototype.initialize = function () {
151 this.clear();
152 this.upload = this.createUpload();
153 this.setPage( 'upload' );
154 };
155
156 /**
157 * Create a new upload model
158 *
159 * @protected
160 * @return {mw.Upload} Upload model
161 */
162 mw.Upload.BookletLayout.prototype.createUpload = function () {
163 return new mw.Upload();
164 };
165
166 /* Uploading */
167
168 /**
169 * Uploads the file that was added in the upload form. Uses
170 * {@link #getFile getFile} to get the HTML5
171 * file object.
172 *
173 * @protected
174 * @fires fileUploaded
175 * @return {jQuery.Promise}
176 */
177 mw.Upload.BookletLayout.prototype.uploadFile = function () {
178 var file = this.getFile();
179
180 this.filenameWidget.setValue( file.name );
181 this.setPage( 'info' );
182
183 this.upload.setFile( file );
184 this.uploadPromise = this.upload.uploadToStash();
185 this.uploadPromise.then( this.emit.bind( this, 'fileUploaded' ) );
186
187 return this.uploadPromise;
188 };
189
190 /**
191 * Saves the stash finalizes upload. Uses
192 * {@link #getFilename getFilename}, and
193 * {@link #getText getText} to get details from
194 * the form.
195 *
196 * @protected
197 * @fires fileSaved
198 * @returns {jQuery.Promise} Rejects the promise with an
199 * {@link OO.ui.Error error}, or resolves if the upload was successful.
200 */
201 mw.Upload.BookletLayout.prototype.saveFile = function () {
202 var layout = this,
203 deferred = $.Deferred();
204
205 this.upload.setFilename( this.getFilename() );
206 this.upload.setText( this.getText() );
207
208 this.uploadPromise.always( function () {
209
210 if ( layout.upload.getState() === mw.Upload.State.ERROR ) {
211 deferred.reject( new OO.ui.Error( mw.msg( 'upload-process-error' ) ) );
212 return false;
213 }
214
215 if ( layout.upload.getState() === mw.Upload.State.WARNING ) {
216 deferred.reject( new OO.ui.Error( mw.msg( 'upload-process-error' ) ) );
217 return false;
218 }
219
220 layout.upload.finishStashUpload().always( function () {
221 var name;
222
223 if ( layout.upload.getState() === mw.Upload.State.ERROR ) {
224 deferred.reject( new OO.ui.Error( mw.msg( 'upload-process-error' ) ) );
225 return false;
226 }
227
228 if ( layout.upload.getState() === mw.Upload.State.WARNING ) {
229 deferred.reject( new OO.ui.Error( mw.msg( 'upload-process-warning' ) ) );
230 return false;
231 }
232
233 // Normalize page name and localise the 'File:' prefix
234 name = new mw.Title( 'File:' + layout.upload.getFilename() ).toString();
235 layout.filenameUsageWidget.setValue( '[[' + name + ']]' );
236 layout.setPage( 'insert' );
237
238 deferred.resolve();
239 layout.emit( 'fileSaved' );
240 } );
241 } );
242
243 return deferred.promise();
244 };
245
246 /* Form renderers */
247
248 /**
249 * Renders and returns the upload form and sets the
250 * {@link #uploadForm uploadForm} property.
251 *
252 * @protected
253 * @fires selectFile
254 * @returns {OO.ui.FormLayout}
255 */
256 mw.Upload.BookletLayout.prototype.renderUploadForm = function () {
257 var fieldset;
258
259 this.selectFileWidget = new OO.ui.SelectFileWidget();
260 fieldset = new OO.ui.FieldsetLayout( { label: mw.msg( 'upload-form-label-select-file' ) } );
261 fieldset.addItems( [ this.selectFileWidget ] );
262 this.uploadForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
263
264 // Validation
265 this.selectFileWidget.on( 'change', this.onUploadFormChange.bind( this ) );
266
267 return this.uploadForm;
268 };
269
270 /**
271 * Handle change events to the upload form
272 *
273 * @protected
274 * @fires uploadValid
275 */
276 mw.Upload.BookletLayout.prototype.onUploadFormChange = function () {
277 this.emit( 'uploadValid', !!this.selectFileWidget.getValue() );
278 };
279
280 /**
281 * Renders and returns the information form for collecting
282 * metadata and sets the {@link #infoForm infoForm}
283 * property.
284 *
285 * @protected
286 * @returns {OO.ui.FormLayout}
287 */
288 mw.Upload.BookletLayout.prototype.renderInfoForm = function () {
289 var fieldset;
290
291 this.filenameWidget = new OO.ui.TextInputWidget( {
292 indicator: 'required',
293 required: true,
294 validate: /.+/
295 } );
296 this.descriptionWidget = new OO.ui.TextInputWidget( {
297 indicator: 'required',
298 required: true,
299 validate: /.+/,
300 multiline: true,
301 autosize: true
302 } );
303
304 fieldset = new OO.ui.FieldsetLayout( {
305 label: mw.msg( 'upload-form-label-infoform-title' )
306 } );
307 fieldset.addItems( [
308 new OO.ui.FieldLayout( this.filenameWidget, {
309 label: mw.msg( 'upload-form-label-infoform-name' ),
310 align: 'top'
311 } ),
312 new OO.ui.FieldLayout( this.descriptionWidget, {
313 label: mw.msg( 'upload-form-label-infoform-description' ),
314 align: 'top'
315 } )
316 ] );
317 this.infoForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
318
319 this.filenameWidget.on( 'change', this.onInfoFormChange.bind( this ) );
320 this.descriptionWidget.on( 'change', this.onInfoFormChange.bind( this ) );
321
322 return this.infoForm;
323 };
324
325 /**
326 * Handle change events to the info form
327 *
328 * @protected
329 * @fires infoValid
330 */
331 mw.Upload.BookletLayout.prototype.onInfoFormChange = function () {
332 var layout = this;
333 $.when(
334 this.filenameWidget.getValidity(),
335 this.descriptionWidget.getValidity()
336 ).done( function () {
337 layout.emit( 'infoValid', true );
338 } ).fail( function () {
339 layout.emit( 'infoValid', false );
340 } );
341 };
342
343 /**
344 * Renders and returns the insert form to show file usage and
345 * sets the {@link #insertForm insertForm} property.
346 *
347 * @protected
348 * @returns {OO.ui.FormLayout}
349 */
350 mw.Upload.BookletLayout.prototype.renderInsertForm = function () {
351 var fieldset;
352
353 this.filenameUsageWidget = new OO.ui.TextInputWidget();
354 fieldset = new OO.ui.FieldsetLayout( {
355 label: mw.msg( 'upload-form-label-usage-title' )
356 } );
357 fieldset.addItems( [
358 new OO.ui.FieldLayout( this.filenameUsageWidget, {
359 label: mw.msg( 'upload-form-label-usage-filename' ),
360 align: 'top'
361 } )
362 ] );
363 this.insertForm = new OO.ui.FormLayout( { items: [ fieldset ] } );
364
365 return this.insertForm;
366 };
367
368 /* Getters */
369
370 /**
371 * Gets the file object from the
372 * {@link #uploadForm upload form}.
373 *
374 * @protected
375 * @returns {File|null}
376 */
377 mw.Upload.BookletLayout.prototype.getFile = function () {
378 return this.selectFileWidget.getValue();
379 };
380
381 /**
382 * Gets the file name from the
383 * {@link #infoForm information form}.
384 *
385 * @protected
386 * @returns {string}
387 */
388 mw.Upload.BookletLayout.prototype.getFilename = function () {
389 return this.filenameWidget.getValue();
390 };
391
392 /**
393 * Gets the page text from the
394 * {@link #infoForm information form}.
395 *
396 * @protected
397 * @returns {string}
398 */
399 mw.Upload.BookletLayout.prototype.getText = function () {
400 return this.descriptionWidget.getValue();
401 };
402
403 /* Setters */
404
405 /**
406 * Clear the values of all fields
407 *
408 * @protected
409 */
410 mw.Upload.BookletLayout.prototype.clear = function () {
411 this.selectFileWidget.setValue( null );
412 this.filenameWidget.setValue( null ).setValidityFlag( true );
413 this.descriptionWidget.setValue( null ).setValidityFlag( true );
414 this.filenameUsageWidget.setValue( null );
415 };
416
417 }( jQuery, mediaWiki ) );