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.