PageSourceSearch

https://www.etsi.org/wp-content/plugins/ninja-forms-uploads/assets…/controllers/fieldFile.js?ver=3.3.33

js etsi.org collected 2026-09-24 06:58:22 UTC 15,933 bytes, 495 lines download raw bytes

1(function( $ ) {
2	var nfRadio = Backbone.Radio;
3	var radioChannel = nfRadio.channel( 'file_upload' );
4
5	var fileModel = Backbone.Model.extend( {
6		id: 0,
7		name: '',
8		tmpName: '',
9		fieldID: 0
10	} );
11
12	var FileCollection = Backbone.Collection.extend( {
13		model: fileModel
14	} );
15
16	var fileView = Marionette.ItemView.extend( {
17		tagName: 'nf-section',
18		template: '#tmpl-nf-field-file-row',
19
20		events: {
21			'click .delete': 'clickDelete'
22		},
23
24		clickDelete: function( event ) {
25			radioChannel.trigger( 'click:deleteFile', event, this.model );
26		}
27
28	} );
29
30	var fileCollectionView = Marionette.CollectionView.extend( {
31		childView: fileView
32	} );
33
34	var uploadController = Marionette.Object.extend( {
35
36		jqXHR: [],
37		$progress_bars: [],
38		activeUploads: {},
39
40		initialize: function() {
41			this.listenTo( radioChannel, 'init:model', this.initFile );
42			this.listenTo( radioChannel, 'render:view', this.initFileUpload );
43			this.listenTo( radioChannel, 'click:deleteFile', this.deleteFile );
44			radioChannel.reply( 'validate:required', this.validateRequired );
45			radioChannel.reply( 'get:submitData', this.getSubmitData );
46		},
47
48		initFile: function( model ) {
49			model.set( 'uploadMulti', 1 != model.get( 'upload_multi_count' ) ) ;
50		},
51
52		renderView: function( view ) {
53			var el = $( view.el ).find( '.files_uploaded' );
54			view.fileCollectionView = new fileCollectionView( {
55				el: el,
56				collection: view.model.get( 'files' ),
57				thisModel: this.model
58			} );
59
60			view.model.bind( 'change:files', this.changeCollection, view );
61
62			/*
63			 * This radio responder is only necessary if we have Multi-Part Forms active.
64			 * Thankfully, it won't fire if the add-on isn't active.
65			 *
66			 * When we change our parts in a Multi-Part Form, re-render our file collection.
67			 */
68			view.listenTo( nfRadio.channel( 'nfMP' ), 'change:part', this.changeCollection, view );
69			
70			/*
71			 * This will be triggered when a repeater fieldset is adding a fieldset
72			 * It prevents the files data already loaded not to be removed from the view
73			 */
74			if(this.isInRepeaterFieldset(view.el) && view.model.attributes.files.models.length > 0){
75				view.fileCollectionView.render();
76			}
77		},
78
79		isInRepeaterFieldset: function( element ) {
80			return element.dataset.fieldId.includes('.') && element.dataset.fieldId.includes('_');
81		},
82
83		changeCollection: function() {
84			this.fileCollectionView.render();
85			//Set values in repeater when refreshing the files data view
86			nfRadio.channel( "field-repeater" ).trigger( 'set:value' );
87		},
88
89		getFieldID: function( e ) {
90			var $parent = $( e.target ).parents( '.field-wrap' );
91
92			return $parent.data( 'field-id' );
93		},
94
95		getProgressBar: function( e ) {
96			var fieldID = this.getFieldID( e );
97
98			return this.$progress_bars[ fieldID ];
99		},
100
101		resetProgress: function( e ) {
102			var self = this;
103			setTimeout( function() {
104				self.getProgressBar( e ).css( 'width', 0 );
105			}, 1500 );
106		},
107
108		checkFilesLimit: function( view, e, data ) {
109			var limit = view.model.get( 'upload_multi_count' );
110
111			if ( 1 == limit ) {
112				return true;
113			}
114
115			var files = view.model.get( 'files' );
116
117			if ( ( files && files.length >= limit ) || data.files.length > limit ) {
118				var error_msg = nf_upload.strings.file_limit.replace( '%n', limit );
119				alert( error_msg );
120				return false;
121			}
122
123			return true;
124		},
125
126		addActiveUpload: function( formID, fieldID ) {
127			if ( !this.activeUploads[ formID ] ) {
128				this.activeUploads[ formID ] = {};
129			}
130			if ( !this.activeUploads[ formID ][ fieldID ] ) {
131				this.activeUploads[ formID ][ fieldID ] = 1;
132			} else {
133				this.activeUploads[ formID ][ fieldID ] += 1;
134			}
135		},
136
137		removeActiveUpload: function( formID, fieldID ) {
138			if ( this.activeUploads[ formID ] && this.activeUploads[ formID ][ fieldID ] ) {
139				this.activeUploads[ formID ][ fieldID ] -= 1;
140				if ( this.activeUploads[ formID ][ fieldID ] < 1 ) {
141					delete this.activeUploads[ formID ][ fieldID ];
142					var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID );
143					if ( fieldModel ) {
144						var errors = fieldModel.get( 'errors' );
145						var targetError = errors.get( 'upload-in-progress' );
146						if ( targetError ) {
147							errors.remove( targetError );
148							fieldModel.set( 'errors', errors );
149							fieldModel.trigger( 'change:errors', fieldModel );
150						}
151					}
152					var formModel = nfRadio.channel( 'app' ).request( 'get:form', formID );
153					if ( formModel ) {
154						var fieldErrors = formModel.get( 'fieldErrors' );
155						if ( fieldErrors[ fieldID ] ) {
156							delete fieldErrors[ fieldID ][ 'upload-in-progress' ];
157							if ( 0 === _.size( fieldErrors[ fieldID ] ) ) {
158								delete fieldErrors[ fieldID ];
159							}
160						}
161					}
162				}
163				
164				// If no more uploads for this form, enable submit
165				if ( Object.keys( this.activeUploads[ formID ] ).length === 0 ) {
166					delete this.activeUploads[ formID ];
167					nfRadio.channel( 'form-' + formID ).trigger( 'enable:submit' );
168				}
169			}
170		},
171
172		hasActiveUploads: function( formID ) {
173			return this.activeUploads[ formID ] && Object.keys( this.activeUploads[ formID ] ).length > 0;
174		},
175
176		maybeSubmitFieldData: function( data ) {
177			if ( data.paramName === 'files-' + data.formData.field_id || data.paramName === 'files-' + data.formData.field_id + '[]' ) {
178				this.jqXHR[ data.formData.field_id ] = data.submit();
179			}
180		},
181
182		showError: function( error, view ) {
183			nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', error );
184		},
185
186		resetError: function( view, fieldID  ) {
187			nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, 'upload-file-error' );
188			this.$progress_bars[ fieldID ].css( 'width', 0 );
189			$( view.el ).find( '.nf-fu-button-cancel' ).hide();
190			var formID = view.model.get( 'formID' );
191			this.removeActiveUpload( formID, fieldID );
192		},
193
194		cancelUpload: function( e ) {
195			e.preventDefault();
196			var fieldID = e.data.view.model.id;
197			e.data.controller.jqXHR[ fieldID ].abort();
198			// Cleanup is handled by the fileuploadfail event handler on abort
199		},
200
201		isNonceValid: function( view ) {
202			var nonceExpiry = view.model.get( 'uploadNonceExpiry' );
203			if ( typeof nonceExpiry === 'undefined' || nonceExpiry === null ) {
204				return false
205			}
206
207			var now = Math.round( (new Date()).getTime() / 1000 );
208
209			return now < nonceExpiry;
210		},
211
212		getValidNonce: function( field_id ) {
213			return jQuery.post( {
214				url: nfFrontEnd.adminAjax + '?action=nf_fu_get_new_nonce',
215				type: 'POST',
216				data: {
217					'field_id': field_id
218				},
219				cache: false
220			} );
221		},
222
223		initFileUpload: function( view ) {
224			var fieldID = view.model.id;
225			var formID = view.model.get( 'formID' );
226			var nonce = view.model.get( 'uploadNonce' );
227			var $file = $( view.el ).find( '.nf-element' );
228			var $files_uploaded = $( view.el ).find( '.files_uploaded' );
229			this.$progress_bars[ fieldID ] = $( view.el ).find( '.nf-fu-progress-bar' );
230			var url = nfFrontEnd.adminAjax + '?action=nf_fu_upload';
231			var self = this;
232			var files = view.model.get( 'files' );
233
234			$( view.el ).find( 'button.nf-fu-button-cancel' ).on( 'click', { view: view, controller: self }, self.cancelUpload );
235
236			$( view.el ).find( 'button.nf-fu-fileinput-button' ).on( 'click', function() {
237				// Don't show required validation error on click of the file upload button
238				view.model.set( 'firstTouch', false );
239			} );
240
241			/*
242			 * Make sure that our files array isn't undefined.
243			 * If it is, set it to an empty array.
244			 */
245			files = files || [];
246
247			/*
248			 * If "files" isn't a collection, turn it into one.
249			 */
250			if ( ! ( files instanceof FileCollection ) ) {
251				files = new FileCollection( files );
252				view.model.set( 'files', files );
253			}
254
255			this.renderView( view );
256
257			var formData = {
258				form_id: formID,
259				field_id: fieldID,
260				nonce: nonce,
261				abort: false
262			};
263
264			$file.fileupload( {
265				url: url,
266				dataType: 'json',
267				messages: {
268					maxFileSize: nf_upload.strings.max_file_size_error.replace( '%n', view.model.get( 'max_file_size_mb' ) ),
269					minFileSize: nf_upload.strings.min_file_size_error.replace( '%n', view.model.get( 'min_file_size_mb' ) )
270				},
271				maxChunkSize: view.model.get( 'max_chunk_size' ),
272				dropZone: $( view.el ),
273				maxFileSize: view.model.get( 'max_file_size' ),
274				minFileSize: view.model.get( 'min_file_size' ),
275				add: function (e, data) {
276					$( view.el ).find( '.nf-fu-button-cancel' ).show();
277					formData.abort = false;
278					data.formData = formData;
279					if ( self.isNonceValid( view ) ) {
280						self.maybeSubmitFieldData( data );
281
282						return;
283					}
284
285					self.getValidNonce( formData.field_id ).done( function( response ) {
286						if ( !response.success ) {
287							self.showError( nf_upload.strings.upload_nonce_error, view );
288							return false;
289						}
290
291						view.model.set( 'uploadNonce', response.data.nonce );
292						view.model.set( 'uploadNonceExpiry', response.data.nonce_expiry );
293
294						data.formData.nonce = response.data.nonce;
295						self.maybeSubmitFieldData( data );
296					} );
297				},
298				change: function( e, data ) {
299					if ( !self.checkFilesLimit( view, e, data ) ) {
300						return false;
301					}
302					// Remove any errors on this field.
303					nfRadio.channel( 'fields' ).request( 'remove:error', view.model.get( 'id' ), 'required-error' );
304				},
305				drop: function( e, data ) {
306					if ( !self.checkFilesLimit( view, e, data ) ) {
307						return false;
308					}
309				},
310				done: function( e, data ) {
311					self.removeActiveUpload( formID, fieldID );
312
313					if ( !data.result || data.result === undefined ) {
314						self.showError( nf_upload.strings.unknown_upload_error, view );
315						self.resetProgress( e );
316						return;
317					}
318
319					if ( -1 === data.result ) {
320						self.showError( nf_upload.strings.upload_error, view );
321						self.resetProgress( e );
322						return;
323					}
324
325					// Check for errors
326					if ( data.result.errors.length ) {
327						$.each( data.result.errors, function( index, error ) {
328							self.showError( error, view );
329						} );
330					}
331
332					if ( data.result.data.files === undefined || !data.result.data.files.length ) {
333						self.resetProgress( e );
334
335						return;
336					}
337
338					var allowed = view.model.get( 'upload_multi_count' );
339					var limit = 1;
340
341					if ( 1 != allowed ) {
342						var uploaded = view.model.get( 'files' ).length;
343						limit = allowed - uploaded;
344
345						if ( limit <= 0 ) {
346							var error_msg = nf_upload.strings.file_limit.replace( '%n', allowed );
347							self.showError( error_msg, view );
348							self.resetProgress( e );
349							return;
350						}
351					}
352
353					$( view.el ).find( '.nf-fu-button-cancel' ).hide();
354
355					var count = 0;
356					$.each( data.result.data.files, function( index, file ) {
357						count++;
358						if ( count > limit ) {
359							return false;
360						}
361						files.add( new fileModel( { name: file.name, tmp_name: file.tmp_name, fieldID: fieldID } ) );
362					} );
363
364					view.model.set( 'files', files );
365					view.model.set( 'value', 1 );
366					view.model.trigger( 'change:files', view.model );
367
368					self.resetProgress( e );
369
370					nfRadio.channel( 'fields' ).trigger( 'change:field', view.el, view.model );
371				},
372				start: function() {
373					if ( 1 == view.model.get( 'upload_multi_count' ) ) {
374						// Remove the files uploaded display and reset the collection
375						$files_uploaded.empty();
376						files.reset();
377					}
378					nfRadio.channel( 'fields' ).request( 'remove:error', view.model.id, 'upload-file-error' );
379					self.addActiveUpload( formID, fieldID );
380					var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', fieldID );
381					if ( fieldModel ) {
382						var errors = fieldModel.get( 'errors' );
383						errors.add( { 'id': 'upload-in-progress', 'msg': nf_upload.strings.upload_in_progress } );
384						fieldModel.set( 'errors', errors );
385						fieldModel.trigger( 'change:errors', fieldModel );
386					}
387					var formModel = nfRadio.channel( 'app' ).request( 'get:form', formID );
388					if ( formModel ) {
389						var fieldErrors = formModel.get( 'fieldErrors' );
390						if ( 'undefined' === typeof fieldErrors[ fieldID ] ) {
391							fieldErrors[ fieldID ] = {};
392						}
393						fieldErrors[ fieldID ][ 'upload-in-progress' ] = nf_upload.strings.upload_in_progress;
394					}
395					nfRadio.channel( 'form-' + formID ).trigger( 'disable:submit', view.model );
396				},
397				progressall: function( e, data ) {
398					var progress = parseInt( data.loaded / data.total * 100, 10 );
399					self.getProgressBar( e ).css( 'width', progress + '%' );
400				}
401			} ).on( 'fileuploadprocessalways', function( e, data ) {
402				var index = data.index,
403					file = data.files[ index ];
404				if ( file.error ) {
405					nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', file.error );
406					self.removeActiveUpload( formID, fieldID );
407				}
408			} ).on( 'fileuploadchunkdone', function( e, data ) {
409				// Check for errors
410				if ( data.result.errors.length ) {
411					$.each( data.result.errors, function( index, error ) {
412						self.showError( error, view );
413					} );
414
415					self.jqXHR[ formData.field_id ].abort();
416					self.$progress_bars[  formData.field_id ].css( 'width', 0 );
417					self.removeActiveUpload( formID, fieldID );
418					formData.abort = true;
419					return;
420				}
421
422				var key = data.result.data.files[ 0 ][ 'new_tmp_key' ];
423				formData[ key ] = data.result.data.files[ 0 ][ 'tmp_name' ];
424			} ).on( "fileuploadchunksend", function( e, data ) {
425				if ( formData.abort === true ) {
426					return false;
427				}
428			} ).on( 'fileuploadfail', function( e, data ) {
429				if ( data.errorThrown === 'abort' ) {
430					// Upload was cancelled via the Cancel button — perform targeted cleanup.
431					// resetError() handles progress bar, cancel button visibility, and active upload tracking.
432					self.resetError( view, fieldID );
433					return false;
434				}
435				// Genuine upload failure — clean up tracking and progress display.
436				self.removeActiveUpload( formID, fieldID );
437				self.resetProgress( e );
438			} )
439				.prop( 'disabled', !$.support.fileInput )
440				.parent().addClass( $.support.fileInput ? undefined : 'disabled' );
441		},
442
443		getSubmitData: function( fieldData, field ) {
444			fieldData.files = field.get( 'files' );
445
446			return fieldData;
447		},
448
449		deleteFile: function( event, model ) {
450			event.preventDefault();
451			model.collection.remove( model );
452			// send off AJAX request to delete temp file or uploaded file
453
454			var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'fieldID' ) );
455			nfRadio.channel( 'fields' ).trigger( 'change:field', '', fieldModel );
456			// Remove any errors on this field.
457			nfRadio.channel( 'fields' ).request( 'remove:error', fieldModel, 'required-error' );
458		},
459
460		/**
461		 * Check files have been submitted successfully for required field check
462		 *
463		 * @param el
464		 * @param model
465		 * @returns {boolean}
466		 */
467		validateRequired: function( el, model ) {
468			if ( !model.get( 'firstTouch' ) ) {
469				model.set( 'firstTouch', true );
470				return true;
471			}
472
473			var files = model.get( 'files' );
474			if ( typeof files === 'undefined' || !files.length ) {
475				model.set( 'value', '' );
476				return false;
477			}
478
479			return true;
480		}
481
482	} );
483
484	new uploadController();
485
486	$( document ).ready( function() {
487		$( 'body' ).on( 'click', 'button.nf-fu-fileinput-button', function( e ) {
488			$( this ).parent().find( 'input.nf-element' ).click();
489		} );
490
491		$( document ).bind( 'drop dragover', function( e ) {
492			e.preventDefault();
493		} );
494	} );
495})( jQuery );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.