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 39 initialize: function() { 40 this.listenTo( radioChannel, 'init:model', this.initFile ); 41 this.listenTo( radioChannel, 'render:view', this.initFileUpload ); 42 this.listenTo( radioChannel, 'click:deleteFile', this.deleteFile ); 43 radioChannel.reply( 'validate:required', this.validateRequired ); 44 radioChannel.reply( 'get:submitData', this.getSubmitData ); 45 }, 46 47 initFile: function( model ) { 48 model.set( 'uploadMulti', 1 != model.get( 'upload_multi_count' ) ) ; 49 }, 50 51 renderView: function( view ) { 52 var el = $( view.el ).find( '.files_uploaded' ); 53 view.fileCollectionView = new fileCollectionView( { 54 el: el, 55 collection: view.model.get( 'files' ), 56 thisModel: this.model 57 } ); 58 59 view.model.bind( 'change:files', this.changeCollection, view ); 60 61 /* 62 * This radio responder is only necessary if we have Multi-Part Forms active. 63 * Thankfully, it won't fire if the add-on isn't active. 64 * 65 * When we change our parts in a Multi-Part Form, re-render our file collection. 66 */ 67 view.listenTo( nfRadio.channel( 'nfMP' ), 'change:part', this.changeCollection, view ); 68 }, 69 70 changeCollection: function() { 71 this.fileCollectionView.render(); 72 }, 73 74 getFieldID: function( e ) { 75 var $parent = $( e.target ).parents( '.field-wrap' ); 76 77 return $parent.data( 'field-id' ); 78 }, 79 80 getProgressBar: function( e ) { 81 var fieldID = this.getFieldID( e ); 82 83 return this.$progress_bars[ fieldID ]; 84 }, 85 86 resetProgress: function( e ) { 87 var self = this; 88 setTimeout( function() { 89 self.getProgressBar( e ).css( 'width', 0 ); 90 }, 1500 ); 91 }, 92 93 checkFilesLimit: function( view, e, data ) { 94 var limit = view.model.get( 'upload_multi_count' ); 95 96 if ( 1 == limit ) { 97 return true; 98 } 99 100 var files = view.model.get( 'files' ); 101 102 if ( ( files && files.length >= limit ) || data.files.length > limit ) { 103 var error_msg = nf_upload.strings.file_limit.replace( '%n', limit ); 104 alert( error_msg ); 105 return false; 106 } 107 108 return true; 109 }, 110 111 maybeSubmitFieldData: function( data ) { 112 if ( data.paramName === 'files-' + data.formData.field_id || data.paramName === 'files-' + data.formData.field_id + '[]' ) { 113 this.jqXHR[ data.formData.field_id ] = data.submit(); 114 } 115 }, 116 117 showError: function( error, view ) { 118 nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', error ); 119 }, 120 121 resetError: function( view, fieldID ) { 122 nfRadio.channel( 'fields' ).request( 'remove:error', fieldID, 'upload-file-error' ); 123 this.$progress_bars[ fieldID ].css( 'width', 0 ); 124 $( view.el ).find( '.nf-fu-button-cancel' ).hide(); 125 var formID = view.model.get( 'formID' ); 126 nfRadio.channel( 'form-' + formID ).trigger( 'enable:submit', view.model ); 127 }, 128 129 cancelUpload: function( e ) { 130 var fieldID = e.data.view.model.id; 131 e.data.controller.jqXHR[ fieldID ].abort(); 132 e.data.controller.resetError( e.data.view, fieldID ); 133 }, 134 135 isNonceValid: function( view ) { 136 var nonceExpiry = view.model.get( 'uploadNonceExpiry' ); 137 if ( typeof nonceExpiry === 'undefined' || nonceExpiry === null ) { 138 return false 139 } 140 141 var now = Math.round( (new Date()).getTime() / 1000 ); 142 143 return now < nonceExpiry; 144 }, 145 146 getValidNonce: function( field_id ) { 147 return jQuery.post( { 148 url: nfFrontEnd.adminAjax + '?action=nf_fu_get_new_nonce', 149 type: 'POST', 150 data: { 151 'field_id': field_id 152 }, 153 cache: false 154 } ); 155 }, 156 157 initFileUpload: function( view ) { 158 var fieldID = view.model.id; 159 var formID = view.model.get( 'formID' ); 160 var nonce = view.model.get( 'uploadNonce' ); 161 var $file = $( view.el ).find( '.nf-element' ); 162 var $files_uploaded = $( view.el ).find( '.files_uploaded' ); 163 this.$progress_bars[ fieldID ] = $( view.el ).find( '.nf-fu-progress-bar' ); 164 var url = nfFrontEnd.adminAjax + '?action=nf_fu_upload'; 165 var self = this; 166 var files = view.model.get( 'files' ); 167 168 $( view.el ).find( 'button.nf-fu-button-cancel' ).on( 'click', { view: view, controller: self }, self.cancelUpload ); 169 170 $( view.el ).find( 'button.nf-fu-fileinput-button' ).on( 'click', function() { 171 // Don't show required validation error on click of the file upload button 172 view.model.set( 'firstTouch', false ); 173 } ); 174 175 /* 176 * Make sure that our files array isn't undefined. 177 * If it is, set it to an empty array. 178 */ 179 files = files || []; 180 181 /* 182 * If "files" isn't a collection, turn it into one. 183 */ 184 if ( ! ( files instanceof FileCollection ) ) { 185 files = new FileCollection( files ); 186 view.model.set( 'files', files ); 187 } 188 189 this.renderView( view ); 190 191 var formData = { 192 form_id: formID, 193 field_id: fieldID, 194 nonce: nonce, 195 abort: false 196 }; 197 198 $file.fileupload( { 199 url: url, 200 dataType: 'json', 201 messages: { 202 maxFileSize: nf_upload.strings.max_file_size_error.replace( '%n', view.model.get( 'max_file_size_mb' ) ), 203 minFileSize: nf_upload.strings.min_file_size_error.replace( '%n', view.model.get( 'min_file_size_mb' ) ) 204 }, 205 maxChunkSize: view.model.get( 'max_chunk_size' ), 206 dropZone: $( view.el ), 207 maxFileSize: view.model.get( 'max_file_size' ), 208 minFileSize: view.model.get( 'min_file_size' ), 209 add: function (e, data) { 210 $( view.el ).find( '.nf-fu-button-cancel' ).show(); 211 formData.abort = false;
212 data.formData = formData; 213 if ( self.isNonceValid( view ) ) { 214 self.maybeSubmitFieldData( data ); 215 216 return; 217 } 218 219 self.getValidNonce( formData.field_id ).done( function( response ) { 220 if ( !response.success ) { 221 self.showError( nf_upload.strings.upload_nonce_error, view ); 222 return false; 223 } 224 225 view.model.set( 'uploadNonce', response.data.nonce ); 226 view.model.set( 'uploadNonceExpiry', response.data.nonce_expiry ); 227 228 data.formData.nonce = response.data.nonce; 229 self.maybeSubmitFieldData( data ); 230 } ); 231 }, 232 change: function( e, data ) { 233 if ( !self.checkFilesLimit( view, e, data ) ) { 234 return false; 235 } 236 // Remove any errors on this field. 237 nfRadio.channel( 'fields' ).request( 'remove:error', view.model.get( 'id' ), 'required-error' ); 238 }, 239 drop: function( e, data ) { 240 if ( !self.checkFilesLimit( view, e, data ) ) { 241 return false; 242 } 243 }, 244 done: function( e, data ) { 245 if ( !data.result || data.result === undefined ) { 246 self.showError( nf_upload.strings.unknown_upload_error, view ); 247 self.resetProgress( e ); 248 return; 249 } 250 251 if ( -1 === data.result ) { 252 self.showError( nf_upload.strings.upload_error, view ); 253 self.resetProgress( e ); 254 return; 255 } 256 257 // Check for errors 258 if ( data.result.errors.length ) { 259 $.each( data.result.errors, function( index, error ) { 260 self.showError( error, view ); 261 } ); 262 } 263 264 if ( data.result.data.files === undefined || !data.result.data.files.length ) { 265 self.resetProgress( e ); 266 267 return; 268 } 269 270 var allowed = view.model.get( 'upload_multi_count' ); 271 var limit = 1; 272 273 if ( 1 != allowed ) { 274 var uploaded = view.model.get( 'files' ).length; 275 limit = allowed - uploaded; 276 277 if ( limit <= 0 ) { 278 var error_msg = nf_upload.strings.file_limit.replace( '%n', allowed ); 279 self.showError( error_msg, view ); 280 self.resetProgress( e ); 281 return; 282 } 283 } 284 285 $( view.el ).find( '.nf-fu-button-cancel' ).hide(); 286 287 var count = 0; 288 $.each( data.result.data.files, function( index, file ) { 289 count++; 290 if ( count > limit ) { 291 return false; 292 } 293 files.add( new fileModel( { name: file.name, tmp_name: file.tmp_name, fieldID: fieldID } ) ); 294 } ); 295 296 view.model.set( 'files', files ); 297 view.model.trigger( 'change:files', view.model ); 298 view.model.set( 'value', 1 ); 299 300 self.resetProgress( e ); 301 302 nfRadio.channel( 'fields' ).trigger( 'change:field', view.el, view.model ); 303 nfRadio.channel( 'form-' + formID ).trigger( 'enable:submit', view.model ); 304 }, 305 start: function() { 306 if ( 1 == view.model.get( 'upload_multi_count' ) ) { 307 // Remove the files uploaded display and reset the collection 308 $files_uploaded.empty(); 309 files.reset(); 310 } 311 nfRadio.channel( 'fields' ).request( 'remove:error', view.model.id, 'upload-file-error' ); 312 nfRadio.channel( 'form-' + formID ).trigger( 'disable:submit', view.model ); 313 }, 314 progressall: function( e, data ) { 315 var progress = parseInt( data.loaded / data.total * 100, 10 ); 316 self.getProgressBar( e ).css( 'width', progress + '%' ); 317 } 318 } ).on( 'fileuploadprocessalways', function( e, data ) { 319 var index = data.index, 320 file = data.files[ index ]; 321 if ( file.error ) { 322 nfRadio.channel( 'fields' ).request( 'add:error', view.model.id, 'upload-file-error', file.error ); 323 } 324 } ).on( 'fileuploadchunkdone', function( e, data ) { 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 self.jqXHR[ formData.field_id ].abort(); 332 self.$progress_bars[ formData.field_id ].css( 'width', 0 ); 333 formData.abort = true; 334 return; 335 } 336 337 var key = data.result.data.files[ 0 ][ 'new_tmp_key' ]; 338 formData[ key ] = data.result.data.files[ 0 ][ 'tmp_name' ]; 339 } ).on( "fileuploadchunksend", function( e, data ) { 340 if ( formData.abort === true ) { 341 return false;
342 } 343 } ) 344 .prop( 'disabled', !$.support.fileInput ) 345 .parent().addClass( $.support.fileInput ? undefined : 'disabled' ); 346 }, 347 348 getSubmitData: function( fieldData, field ) { 349 fieldData.files = field.get( 'files' ); 350 351 return fieldData; 352 }, 353 354 deleteFile: function( event, model ) { 355 event.preventDefault(); 356 model.collection.remove( model ); 357 // send off AJAX request to delete temp file or uploaded file 358 359 var fieldModel = nfRadio.channel( 'fields' ).request( 'get:field', model.get( 'fieldID' ) ); 360 nfRadio.channel( 'fields' ).trigger( 'change:field', '', fieldModel ); 361 // Remove any errors on this field. 362 nfRadio.channel( 'fields' ).request( 'remove:error', fieldModel, 'required-error' ); 363 }, 364 365 /** 366 * Check files have been submitted successfully for required field check 367 * 368 * @param el 369 * @param model 370 * @returns {boolean} 371 */ 372 validateRequired: function( el, model ) { 373 if ( !model.get( 'firstTouch' ) ) { 374 model.set( 'firstTouch', true ); 375 return true; 376 } 377 378 var files = model.get( 'files' ); 379 if ( typeof files === 'undefined' || !files.length ) { 380 model.set( 'value', '' ); 381 return false; 382 } 383 384 return true; 385 } 386 387 } ); 388 389 new uploadController(); 390 391 $( document ).ready( function() { 392 $( 'body' ).on( 'click', 'button.nf-fu-fileinput-button', function( e ) { 393 $( this ).parent().find( 'input.nf-element' ).click(); 394 } ); 395 396 $( document ).bind( 'drop dragover', function( e ) { 397 e.preventDefault(); 398 } ); 399 } ); 400})( 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.