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.