Subversion Repositories eFlore/Applications.cel

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
27 jpm 1
// vim: ts=4:sw=4:nu:fdc=4:nospell
2
/**
3
 * Ext.ux.FileUploader
4
 *
5
 * @author  Ing. Jozef Sakáloš
6
 * @version $Id: Ext.ux.FileUploader.js 83 2008-03-21 12:54:35Z jozo $
7
 * @date    15. March 2008
8
 *
9
 * @license Ext.ux.FileUploader is licensed under the terms of
10
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
11
 * that the code/component(s) do NOT become part of another Open Source or Commercially
12
 * licensed development library or toolkit without explicit permission.
13
 *
14
 * License details: http://www.gnu.org/licenses/lgpl.html
15
 */
16
 
17
/*global Ext */
18
 
19
/**
20
 * @class Ext.ux.FileUploader
21
 * @extends Ext.util.Observable
22
 * @constructor
23
 */
24
Ext.ux.FileUploader = function(config) {
25
	Ext.apply(this, config);
26
 
27
	// call parent
28
	Ext.ux.FileUploader.superclass.constructor.apply(this, arguments);
29
 
30
	// add events
31
	// {{{
32
	this.addEvents(
33
		/**
34
		 * @event beforeallstart
35
		 * Fires before an upload (of all files) is started. Return false to cancel the event.
36
		 * @param {Ext.ux.FileUploader} this
37
		 */
38
		 'beforeallstart'
39
		/**
40
		 * @event allfinished
41
		 * Fires after upload (of all files) is finished
42
		 * @param {Ext.ux.FileUploader} this
43
		 */
44
		,'allfinished'
45
		/**
46
		 * @event beforefilestart
47
		 * Fires before the file upload is started. Return false to cancel the event.
48
		 * Fires only when singleUpload = false
49
		 * @param {Ext.ux.FileUploader} this
50
		 * @param {Ext.data.Record} record upload of which is being started
51
		 */
52
		,'beforefilestart'
53
		/**
54
		 * @event filefinished
55
		 * Fires when file finished uploading.
56
		 * Fires only when singleUpload = false
57
		 * @param {Ext.ux.FileUploader} this
58
		 * @param {Ext.data.Record} record upload of which has finished
59
		 */
60
		,'filefinished'
61
		/**
62
		 * @event progress
63
		 * Fires when progress has been updated
64
		 * @param {Ext.ux.FileUploader} this
65
		 * @param {Object} data Progress data object
66
		 * @param {Ext.data.Record} record Only if singleUpload = false
67
		 */
68
		,'progress'
69
	);
70
	// }}}
71
 
72
}; // eo constructor
73
 
74
Ext.extend(Ext.ux.FileUploader, Ext.util.Observable, {
75
 
76
	// configuration options
77
	// {{{
78
	/**
79
	 * @cfg {Object} baseParams baseParams are sent to server in each request.
80
	 */
81
	 baseParams:{cmd:'upload',dir:'.'}
82
 
83
	/**
84
	 * @cfg {Boolean} concurrent true to start all requests upon upload start, false to start
85
	 * the next request only if previous one has been completed (or failed). Applicable only if
86
	 * singleUpload = false
87
	 */
88
	,concurrent:true
89
 
90
	/**
91
	 * @cfg {Boolean} enableProgress true to enable querying server for progress information
92
	 */
93
	,enableProgress:true
94
 
95
	/**
96
	 * @cfg {String} jsonErrorText Text to use for json error
97
	 */
98
	,jsonErrorText:'Cannot decode JSON object'
99
 
100
	/**
101
	 * @cfg {Number} Maximum client file size in bytes
102
	 */
103
	,maxFileSize:524288
104
 
105
	/**
106
	 * @cfg {String} progressIdName Name to give hidden field for upload progress identificator
107
	 */
108
	,progressIdName:'UPLOAD_IDENTIFIER'
109
 
110
	/**
111
	 * @cfg {Number} progressInterval How often (in ms) is progress requested from server
112
	 */
113
	,progressInterval:2000
114
 
115
	/**
116
	 * @cfg {String} progressUrl URL to request upload progress from
117
	 */
118
	,progressUrl:'progress.php'
119
 
120
	/**
121
	 * @cfg {Object} progressMap Mapping of received progress fields to store progress fields
122
	 */
123
	,progressMap:{
124
		 bytes_total:'bytesTotal'
125
		,bytes_uploaded:'bytesUploaded'
126
		,est_sec:'estSec'
127
		,files_uploaded:'filesUploaded'
128
		,speed_average:'speedAverage'
129
		,speed_last:'speedLast'
130
		,time_last:'timeLast'
131
		,time_start:'timeStart'
132
	}
133
	/**
134
	 * @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one
135
	 */
136
	,singleUpload:false
137
 
138
	/**
139
	 * @cfg {Ext.data.Store} store Mandatory. Store that holds files to upload
140
	 */
141
 
142
	/**
143
	 * @cfg {String} unknownErrorText Text to use for unknow error
144
	 */
145
	,unknownErrorText:'Unknown error'
146
 
147
	/**
148
	 * @cfg {String} url Mandatory. URL to upload to
149
	 */
150
 
151
	// }}}
152
 
153
	// private
154
	// {{{
155
	/**
156
	 * uploads in progress count
157
	 * @private
158
	 */
159
	,upCount:0
160
	// }}}
161
 
162
	// methods
163
	// {{{
164
	/**
165
	 * creates form to use for upload.
166
	 * @private
167
	 * @return {Ext.Element} form
168
	 */
169
	,createForm:function(record) {
170
		var progressId = parseInt(Math.random() * 1e10, 10);
171
		var form = Ext.getBody().createChild({
172
			 tag:'form'
173
			,action:this.url
174
			,method:'post'
175
			,cls:'x-hidden'
176
			,id:Ext.id()
177
			,cn:[{
178
				 tag:'input'
179
				,type:'hidden'
180
				,name:'APC_UPLOAD_PROGRESS'
181
				,value:progressId
182
			},{
183
				 tag:'input'
184
				,type:'hidden'
185
				,name:this.progressIdName
186
				,value:progressId
187
			},{
188
				 tag:'input'
189
				,type:'hidden'
190
				,name:'MAX_FILE_SIZE'
191
				,value:this.maxFileSize
192
			}]
193
		});
194
		if(record) {
195
			record.set('form', form);
196
			record.set('progressId', progressId);
197
		}
198
		else {
199
			this.progressId = progressId;
200
		}
201
		return form;
202
 
203
	} // eo function createForm
204
	// }}}
205
	// {{{
206
	,deleteForm:function(form, record) {
207
		form.remove();
208
		if(record) {
209
			record.set('form', null);
210
		}
211
	} // eo function deleteForm
212
	// }}}
213
	// {{{
214
	/**
215
	 * Fires event(s) on upload finish/error
216
	 * @private
217
	 */
218
	,fireFinishEvents:function(options) {
219
		if(true !== this.eventsSuspended && !this.singleUpload) {
220
			this.fireEvent('filefinished', this, options && options.record);
221
		}
222
		if(true !== this.eventsSuspended && 0 === this.upCount) {
223
			this.stopProgress();
224
			this.fireEvent('allfinished', this);
225
		}
226
	} // eo function fireFinishEvents
227
	// }}}
228
	// {{{
229
	/**
230
	 * Geg the iframe identified by record
231
	 * @private
232
	 * @param {Ext.data.Record} record
233
	 * @return {Ext.Element} iframe or null if not found
234
	 */
235
	,getIframe:function(record) {
236
		var iframe = null;
237
		var form = record.get('form');
238
		if(form && form.dom && form.dom.target) {
239
			iframe = Ext.get(form.dom.target);
240
		}
241
		return iframe;
242
	} // eo function getIframe
243
	// }}}
244
	// {{{
245
	/**
246
	 * returns options for Ajax upload request
247
	 * @private
248
	 * @param {Ext.data.Record} record
249
	 * @param {Object} params params to add
250
	 */
251
	,getOptions:function(record, params) {
252
		var o = {
253
			 url:this.url
254
			,method:'post'
255
			,isUpload:true
256
			,scope:this
257
			,callback:this.uploadCallback
258
			,record:record
259
			,params:this.getParams(record, params)
260
		};
261
		return o;
262
	} // eo function getOptions
263
	// }}}
264
	// {{{
265
	/**
266
	 * get params to use for request
267
	 * @private
268
	 * @return {Object} params
269
	 */
270
	,getParams:function(record, params) {
271
		var p = {path:this.path};
272
		Ext.apply(p, this.baseParams || {}, params || {});
273
		return p;
274
	}
275
	// }}}
276
	// {{{
277
	/**
278
	 * processes success response
279
	 * @private
280
	 * @param {Object} options options the request was called with
281
	 * @param {Object} response request response object
282
	 * @param {Object} o decoded response.responseText
283
	 */
284
	,processSuccess:function(options, response, o) {
285
		var record = false;
286
 
287
		// all files uploadded ok
288
		if(this.singleUpload) {
289
			this.store.each(function(r) {
290
				r.set('state', 'done');
291
				r.set('error', '');
292
				r.commit();
293
			});
294
		}
295
		else {
296
			record = options.record;
297
			record.set('state', 'done');
298
			record.set('error', '');
299
			record.commit();
300
		}
301
 
302
		this.deleteForm(options.form, record);
303
 
304
	} // eo processSuccess
305
	// }}}
306
	// {{{
307
	/**
308
	 * processes failure response
309
	 * @private
310
	 * @param {Object} options options the request was called with
311
	 * @param {Object} response request response object
312
	 * @param {String/Object} error Error text or JSON decoded object. Optional.
313
	 */
314
	,processFailure:function(options, response, error) {
315
		var record = options.record;
316
		var records;
317
 
318
		// singleUpload - all files uploaded in one form
319
		if(this.singleUpload) {
320
			// some files may have been successful
321
			records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
322
			records.each(function(record) {
323
				var e = error.errors ? error.errors[record.id] : this.unknownErrorText;
324
				if(e) {
325
					record.set('state', 'failed');
326
					record.set('error', e);
327
					Ext.getBody().appendChild(record.get('input'));
328
				}
329
				else {
330
					record.set('state', 'done');
331
					record.set('error', '');
332
				}
333
				record.commit();
334
			}, this);
335
 
336
			this.deleteForm(options.form);
337
		}
338
		// multipleUpload - each file uploaded in it's own form
339
		else {
340
			if(error && 'object' === Ext.type(error)) {
341
				record.set('error', error.errors && error.errors[record.id] ? error.errors[record.id] : this.unknownErrorText);
342
			}
343
			else if(error) {
344
				record.set('error', error);
345
			}
346
			else if(response && response.responseText) {
347
				record.set('error', response.responseText);
348
			}
349
			else {
350
				record.set('error', this.unknownErrorText);
351
			}
352
			record.set('state', 'failed');
353
			record.commit();
354
		}
355
	} // eof processFailure
356
	// }}}
357
	// {{{
358
	/**
359
	 * Delayed task callback
360
	 */
361
	,requestProgress:function() {
362
		var records, p;
363
		var o = {
364
			 url:this.progressUrl
365
			,method:'post'
366
			,params:{}
367
			,scope:this
368
			,callback:function(options, success, response) {
369
				var o;
370
				if(true !== success) {
371
					return;
372
				}
373
				try {
374
					o = Ext.decode(response.responseText);
375
				}
376
				catch(e) {
377
					return;
378
				}
379
				if('object' !== Ext.type(o) || true !== o.success) {
380
					return;
381
				}
382
 
383
				if(this.singleUpload) {
384
					this.progress = {};
385
					for(p in o) {
386
						if(this.progressMap[p]) {
387
							this.progress[this.progressMap[p]] = parseInt(o[p], 10);
388
						}
389
					}
390
					if(true !== this.eventsSuspended) {
391
						this.fireEvent('progress', this, this.progress);
392
					}
393
 
394
				}
395
				else {
396
					for(p in o) {
397
						if(this.progressMap[p] && options.record) {
398
							options.record.set(this.progressMap[p], parseInt(o[p], 10));
399
						}
400
					}
401
					if(options.record) {
402
						options.record.commit();
403
						if(true !== this.eventsSuspended) {
404
							this.fireEvent('progress', this, options.record.data, options.record);
405
						}
406
					}
407
				}
408
				this.progressTask.delay(this.progressInterval);
409
			}
410
		};
411
		if(this.singleUpload) {
412
			o.params[this.progressIdName] = this.progressId;
413
			o.params.APC_UPLOAD_PROGRESS = this.progressId;
414
			Ext.Ajax.request(o);
415
		}
416
		else {
417
			records = this.store.query('state', 'uploading');
418
			records.each(function(r) {
419
				o.params[this.progressIdName] = r.get('progressId');
420
				o.params.APC_UPLOAD_PROGRESS = o.params[this.progressIdName];
421
				o.record = r;
422
				(function() {
423
					Ext.Ajax.request(o);
424
				}).defer(250);
425
			}, this);
426
		}
427
	} // eo function requestProgress
428
	// }}}
429
	// {{{
430
	/**
431
	 * path setter
432
	 * @private
433
	 */
434
	,setPath:function(path) {
435
		this.path = path;
436
	} // eo setPath
437
	// }}}
438
	// {{{
439
	/**
440
	 * url setter
441
	 * @private
442
	 */
443
	,setUrl:function(url) {
444
		this.url = url;
445
	} // eo setUrl
446
	// }}}
447
	// {{{
448
	/**
449
	 * Starts progress fetching from server
450
	 * @private
451
	 */
452
	,startProgress:function() {
453
		if(!this.progressTask) {
454
			this.progressTask = new Ext.util.DelayedTask(this.requestProgress, this);
455
		}
456
		this.progressTask.delay.defer(this.progressInterval / 2, this.progressTask, [this.progressInterval]);
457
	} // eo function startProgress
458
	// }}}
459
	// {{{
460
	/**
461
	 * Stops progress fetching from server
462
	 * @private
463
	 */
464
	,stopProgress:function() {
465
		if(this.progressTask) {
466
			this.progressTask.cancel();
467
		}
468
	} // eo function stopProgress
469
	// }}}
470
	// {{{
471
	/**
472
	 * Stops all currently running uploads
473
	 */
474
	,stopAll:function() {
475
		var records = this.store.query('state', 'uploading');
476
		records.each(this.stopUpload, this);
477
	} // eo function stopAll
478
	// }}}
479
	// {{{
480
	/**
481
	 * Stops currently running upload
482
	 * @param {Ext.data.Record} record Optional, if not set singleUpload = true is assumed
483
	 * and the global stop is initiated
484
	 */
485
	,stopUpload:function(record) {
486
		// single abord
487
		var iframe = false;
488
		if(record) {
489
			iframe = this.getIframe(record);
490
			this.stopIframe(iframe);
491
			this.upCount--;
492
			this.upCount = 0 > this.upCount ? 0 : this.upCount;
493
			record.set('state', 'stopped');
494
			this.fireFinishEvents({record:record});
495
		}
496
		// all abort
497
		else if(this.form) {
498
			iframe = Ext.fly(this.form.dom.target);
499
			this.stopIframe(iframe);
500
			this.upCount = 0;
501
			this.fireFinishEvents();
502
		}
503
 
504
	} // eo function abortUpload
505
	// }}}
506
	// {{{
507
	/**
508
	 * Stops uploading in hidden iframe
509
	 * @private
510
	 * @param {Ext.Element} iframe
511
	 */
512
	,stopIframe:function(iframe) {
513
		if(iframe) {
514
			try {
515
				iframe.dom.contentWindow.stop();
516
				iframe.remove.defer(250, iframe);
517
			}
518
			catch(e){}
519
		}
520
	} // eo function stopIframe
521
	// }}}
522
	// {{{
523
	/**
524
	 * Main public interface function. Preforms the upload
525
	 */
526
	,upload:function() {
527
 
528
		var records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
529
		if(!records.getCount()) {
530
			return;
531
		}
532
 
533
		// fire beforeallstart event
534
		if(true !== this.eventsSuspended && false === this.fireEvent('beforeallstart', this)) {
535
			return;
536
		}
537
		if(this.singleUpload) {
538
			this.uploadSingle();
539
		}
540
		else {
541
			records.each(this.uploadFile, this);
542
		}
543
 
544
		if(true === this.enableProgress) {
545
			this.startProgress();
546
		}
547
 
548
	} // eo function upload
549
	// }}}
550
	// {{{
551
	/**
552
	 * called for both success and failure. Does nearly nothing
553
	 * @private
554
	 * but dispatches processing to processSuccess and processFailure functions
555
	 */
556
	,uploadCallback:function(options, success, response) {
557
 
558
		var o;
559
		this.upCount--;
560
		this.form = false;
561
 
562
		// process ajax success
563
		if(true === success) {
564
			try {
565
				o = Ext.decode(response.responseText);
566
			}
567
			catch(e) {
568
				this.processFailure(options, response, this.jsonErrorText);
569
				this.fireFinishEvents(options);
570
				return;
571
			}
572
			// process command success
573
			if(true === o.success) {
574
				this.processSuccess(options, response, o);
575
			}
576
			// process command failure
577
			else {
578
				this.processFailure(options, response, o);
579
			}
580
		}
581
		// process ajax failure
582
		else {
583
			this.processFailure(options, response);
584
		}
585
 
586
		this.fireFinishEvents(options);
587
 
588
	} // eo function uploadCallback
589
	// }}}
590
	// {{{
591
	/**
592
	 * Uploads one file
593
	 * @param {Ext.data.Record} record
594
	 * @param {Object} params Optional. Additional params to use in request.
595
	 */
596
	,uploadFile:function(record, params) {
597
		// fire beforestart event
598
		if(true !== this.eventsSuspended && false === this.fireEvent('beforefilestart', this, record)) {
599
			return;
600
		}
601
 
602
		// create form for upload
603
		var form = this.createForm(record);
604
 
605
		// append input to the form
606
		var inp = record.get('input');
607
		inp.set({name:inp.id});
608
		form.appendChild(inp);
609
 
610
		// get params for request
611
		var o = this.getOptions(record, params);
612
		o.form = form;
613
 
614
		// set state
615
		record.set('state', 'uploading');
616
		record.set('pctComplete', 0);
617
 
618
		// increment active uploads count
619
		this.upCount++;
620
 
621
		// request upload
622
		Ext.Ajax.request(o);
623
 
624
		// todo:delete after devel
625
		this.getIframe.defer(100, this, [record]);
626
 
627
	} // eo function uploadFile
628
	// }}}
629
	// {{{
630
	/**
631
	 * Uploads all files in single request
632
	 */
633
	,uploadSingle:function() {
634
 
635
		// get records to upload
636
		var records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
637
		if(!records.getCount()) {
638
			return;
639
		}
640
 
641
		// create form and append inputs to it
642
		var form = this.createForm();
643
		records.each(function(record) {
644
			var inp = record.get('input');
645
			inp.set({name:inp.id});
646
			form.appendChild(inp);
647
			record.set('state', 'uploading');
648
		}, this);
649
 
650
		// create options for request
651
		var o = this.getOptions();
652
		o.form = form;
653
 
654
		// save form for stop
655
		this.form = form;
656
 
657
		// increment active uploads counter
658
		this.upCount++;
659
 
660
		// request upload
661
		Ext.Ajax.request(o);
662
 
663
	} // eo function uploadSingle
664
	// }}}
665
 
666
}); // eo extend
667
 
668
// register xtype
669
Ext.reg('fileuploader', Ext.ux.FileUploader);
670
 
671
 // eof