PageSourceSearch

https://brscc.co.uk/wp-content/plugins/ninja-forms-uploads/assets/…/controllers/fieldFile.js?ver=3.3.28

js brscc.co.uk collected 2026-10-02 10:51:43 UTC 13,949 bytes, 460 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				}
143				
144				// If no more uploads for this form, enable submit
145				if ( Object.keys( this.activeUploads[ formID ] ).length === 0 ) {
146					delete this.activeUploads[ formID ];
147					nfRadio.channel( 'form-' + formID ).trigger( 'enable:submit' );
148				}
149			}
150		},
151
152		hasActiveUploads: function( formID ) {
153			return this.activeUploads[ formID ] && Object.keys( this.activeUploads[ formID ] ).length > 0;
154		},
155
156		maybeSubmitFieldData: function( data ) {
157			if ( data.paramName === 'files-' + data.formData.field_id || data.paramName === 'files-' + data.formData.field_id + '[]' ) {
158				this.jqXHR[ data.formData.field_id ] = data.submit();
159			}
160		},
161
162		showError: function( error, view ) {
163			nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', error );
164		},
165
166		resetError: function( view, fieldID  ) {
167			nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, 'upload-file-error' );
168			this.$progress_bars[ fieldID ].css( 'width', 0 );
169			$( view.el ).find( '.nf-fu-button-cancel' ).hide();
170			var formID = view.model.get( 'formID' );
171			this.removeActiveUpload( formID, fieldID );
172		},
173
174		cancelUpload: function( e ) {
175			e.preventDefault();
176			var fieldID = e.data.view.model.id;
177			e.data.controller.jqXHR[ fieldID ].abort();
178			// Cleanup is handled by the fileuploadfail event handler on abort
179		},
180
181		isNonceValid: function( view ) {
182			var nonceExpiry = view.model.get( 'uploadNonceExpiry' );
183			if ( typeof nonceExpiry === 'undefined' || nonceExpiry === null ) {
184				return false
185			}
186
187			var now = Math.round( (new Date()).getTime() / 1000 );
188
189			return now < nonceExpiry;
190		},
191
192		getValidNonce: function( field_id ) {
193			return jQuery.post( {
194				url: nfFrontEnd.adminAjax + '?action=nf_fu_get_new_nonce',
195				type: 'POST',
196				data: {
197					'field_id': field_id
198				},
199				cache: false
200			} );
201		},
202
203		initFileUpload: function( view ) {
204			var fieldID = view.model.id;
205			var formID = view.model.get( 'formID' );
206			var nonce = view.model.get( 'uploadNonce' );
207			var $file = $( view.el ).find( '.nf-element' );
208			var $files_uploaded = $( view.el ).find( '.files_uploaded' );
209			this.$progress_bars[ fieldID ] = $( view.el ).find( '.nf-fu-progress-bar' );
210			var url = nfFrontEnd.adminAjax + '?action=nf_fu_upload';
211			var self = this;
212			var files = view.model.get( 'files' );
213
214			$( view.el ).find( 'button.nf-fu-button-cancel' ).on( 'click', { view: view, controller: self }, self.cancelUpload );
215
216			$( view.el ).find( 'button.nf-fu-fileinput-button' ).on( 'click', function() {
217				// Don't show required validation error on click of the file upload button
218				view.model.set( 'firstTouch', false );
219			} );
220
221			/*
222			 * Make sure that our files array isn't undefined.
223			 * If it is, set it to an empty array.
224			 */
225			files = files || [];
226
227			/*
228			 * If "files" isn't a collection, turn it into one.
229			 */
230			if ( ! ( files instanceof FileCollection ) ) {
231				files = new FileCollection( files );
232				view.model.set( 'files', files );
233			}
234
235			this.renderView( view );
236
237			var formData = {
238				form_id: formID,
239				field_id: fieldID,
240				nonce: nonce,
241				abort: false
242			};
243
244			$file.fileupload( {
245				url: url,
246				dataType: 'json',
247				messages: {
248					maxFileSize: nf_upload.strings.max_file_size_error.replace( '%n', view.model.get( 'max_file_size_mb' ) ),
249					minFileSize: nf_upload.strings.min_file_size_error.replace( '%n', view.model.get( 'min_file_size_mb' ) )
250				},
251				maxChunkSize: view.model.get( 'max_chunk_size' ),
252				dropZone: $( view.el ),
253				maxFileSize: view.model.get( 'max_file_size' ),
254				minFileSize: view.model.get( 'min_file_size' ),
255				add: function (e, data) {
256					$( view.el ).find( '.nf-fu-button-cancel' ).show();
257					formData.abort = false;
258					data.formData = formData;
259					if ( self.isNonceValid( view ) ) {
260						self.maybeSubmitFieldData( data );
261
262						return;
263					}
264
265					self.getValidNonce( formData.field_id ).done( function( response ) {
266						if ( !response.success ) {
267							self.showError( nf_upload.strings.upload_nonce_error, view );
268							return false;
269						}
270
271						view.model.set( 'uploadNonce', response.data.nonce );
272						view.model.set( 'uploadNonceExpiry', response.data.nonce_expiry );
273
274						data.formData.nonce = response.data.nonce;
275						self.maybeSubmitFieldData( data );
276					} );
277				},
278				change: function( e, data ) {
279					if ( !self.checkFilesLimit( view, e, data ) ) {
280						return false;
281					}
282					// Remove any errors on this field.
283					nfRadio.channel( 'fields' ).request( 'remove:error', view.model.get( 'id' ), 'required-error' );
284				},
285				drop: function( e, data ) {
286					if ( !self.checkFilesLimit( view, e, data ) ) {
287						return false;
288					}
289				},
290				done: function( e, data ) {
291					self.removeActiveUpload( formID, fieldID );
292
293					if ( !data.result || data.result === undefined ) {
294						self.showError( nf_upload.strings.unknown_upload_error, view );
295						self.resetProgress( e );
296						return;
297					}
298
299					if ( -1 === data.result ) {
300						self.showError( nf_upload.strings.upload_error, view );
301						self.resetProgress( e );
302						return;
303					}
304
305					// Check for errors
306					if ( data.result.errors.length ) {
307						$.each( data.result.errors, function( index, error ) {
308							self.showError( error, view );
309						} );
310					}
311
312					if ( data.result.data.files === undefined || !data.result.data.files.length ) {
313						self.resetProgress( e );
314
315						return;
316					}
317
318					var allowed = view.model.get( 'upload_multi_count' );
319					var limit = 1;
320
321					if ( 1 != allowed ) {
322						var uploaded = view.model.get( 'files' ).length;
323						limit = allowed - uploaded;
324
325						if ( limit <= 0 ) {
326							var error_msg = nf_upload.strings.file_limit.replace( '%n', allowed );
327							self.showError( error_msg, view );
328							self.resetProgress( e );
329							return;
330						}
331					}
332
333					$( view.el ).find( '.nf-fu-button-cancel' ).hide();
334
335					var count = 0;
336					$.each( data.result.data.files, function( index, file ) {
337						count++;
338						if ( count > limit ) {
339							return false;
340						}
341						files.add( new fileModel( { name: file.name, tmp_name: file.tmp_name, fieldID: fieldID } ) );
342					} );
343
344					view.model.set( 'files', files );
345					view.model.trigger( 'change:files', view.model );
346					view.model.set( 'value', 1 );
347
348					self.resetProgress( e );
349
350					nfRadio.channel( 'fields' ).trigger( 'change:field', view.el, view.model );
351				},
352				start: function() {
353					if ( 1 == view.model.get( 'upload_multi_count' ) ) {
354						// Remove the files uploaded display and reset the collection
355						$files_uploaded.empty();
356						files.reset();
357					}
358					nfRadio.channel( 'fields' ).request( 'remove:error', view.model.id, 'upload-file-error' );
359					self.addActiveUpload( formID, fieldID );
360					nfRadio.channel( 'form-' + formID ).trigger( 'disable:submit', view.model );
361				},
362				progressall: function( e, data ) {
363					var progress = parseInt( data.loaded / data.total * 100, 10 );
364					self.getProgressBar( e ).css( 'width', progress + '%' );
365				}
366			} ).on( 'fileuploadprocessalways', function( e, data ) {
367				var index = data.index,
368					file = data.files[ index ];
369				if ( file.error ) {
370					nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', file.error );
371					self.removeActiveUpload( formID, fieldID );
372				}
373			} ).on( 'fileuploadchunkdone', function( e, data ) {
374				// Check for errors
375				if ( data.result.errors.length ) {
376					$.each( data.result.errors, function( index, error ) {
377						self.showError( error, view );
378					} );
379
380					self.jqXHR[ formData.field_id ].abort();
381					self.$progress_bars[  formData.field_id ].css( 'width', 0 );
382					self.removeActiveUpload( formID, fieldID );
383					formData.abort = true;
384					return;
385				}
386
387				var key = data.result.data.files[ 0 ][ 'new_tmp_key' ];
388				formData[ key ] = data.result.data.files[ 0 ][ 'tmp_name' ];
389			} ).on( "fileuploadchunksend", function( e, data ) {
390				if ( formData.abort === true ) {
391					return false;
392				}
393			} ).on( 'fileuploadfail', function( e, data ) {
394				if ( data.errorThrown === 'abort' ) {
395					// Upload was cancelled via the Cancel button — perform targeted cleanup.
396					// resetError() handles progress bar, cancel button visibility, and active upload tracking.
397					self.resetError( view, fieldID );
398					return false;
399				}
400				// Genuine upload failure — clean up tracking and progress display.
401				self.removeActiveUpload( formID, fieldID );
402				self.resetProgress( e );
403			} )
404				.prop( 'disabled', !$.support.fileInput )
405				.parent().addClass( $.support.fileInput ? undefined : 'disabled' );
406		},
407
408		getSubmitData: function( fieldData, field ) {
409			fieldData.files = field.get( 'files' );
410
411			return fieldData;
412		},
413
414		deleteFile: function( event, model ) {
415			event.preventDefault();
416			model.collection.remove( model );
417			// send off AJAX request to delete temp file or uploaded file
418
419			var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'fieldID' ) );
420			nfRadio.channel( 'fields' ).trigger( 'change:field', '', fieldModel );
421			// Remove any errors on this field.
422			nfRadio.channel( 'fields' ).request( 'remove:error', fieldModel, 'required-error' );
423		},
424
425		/**
426		 * Check files have been submitted successfully for required field check
427		 *
428		 * @param el
429		 * @param model
430		 * @returns {boolean}
431		 */
432		validateRequired: function( el, model ) {
433			if ( !model.get( 'firstTouch' ) ) {
434				model.set( 'firstTouch', true );
435				return true;
436			}
437
438			var files = model.get( 'files' );
439			if ( typeof files === 'undefined' || !files.length ) {
440				model.set( 'value', '' );
441				return false;
442			}
443
444			return true;
445		}
446
447	} );
448
449	new uploadController();
450
451	$( document ).ready( function() {
452		$( 'body' ).on( 'click', 'button.nf-fu-fileinput-button', function( e ) {
453			$( this ).parent().find( 'input.nf-element' ).click();
454		} );
455
456		$( document ).bind( 'drop dragover', function( e ) {
457			e.preventDefault();
458		} );
459	} );
460})( 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.