Subversion Repositories eFlore/Applications.cel

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

// vim: ts=4:sw=4:nu:fdc=4:nospell
/**
 * Ext.ux.FileUploader
 *
 * @author  Ing. Jozef Sakáloš
 * @version $Id: Ext.ux.FileUploader.js 83 2008-03-21 12:54:35Z jozo $
 * @date    15. March 2008
 *
 * @license Ext.ux.FileUploader is licensed under the terms of
 * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
 * that the code/component(s) do NOT become part of another Open Source or Commercially
 * licensed development library or toolkit without explicit permission.
 * 
 * License details: http://www.gnu.org/licenses/lgpl.html
 */

/*global Ext */

/**
 * @class Ext.ux.FileUploader
 * @extends Ext.util.Observable
 * @constructor
 */
Ext.ux.FileUploader = function(config) {
        Ext.apply(this, config);

        // call parent
        Ext.ux.FileUploader.superclass.constructor.apply(this, arguments);

        // add events
        // {{{
        this.addEvents(
                /**
                 * @event beforeallstart
                 * Fires before an upload (of all files) is started. Return false to cancel the event.
                 * @param {Ext.ux.FileUploader} this
                 */
                 'beforeallstart'
                /**
                 * @event allfinished
                 * Fires after upload (of all files) is finished
                 * @param {Ext.ux.FileUploader} this
                 */
                ,'allfinished'
                /**
                 * @event beforefilestart
                 * Fires before the file upload is started. Return false to cancel the event.
                 * Fires only when singleUpload = false
                 * @param {Ext.ux.FileUploader} this
                 * @param {Ext.data.Record} record upload of which is being started
                 */
                ,'beforefilestart'
                /**
                 * @event filefinished
                 * Fires when file finished uploading.
                 * Fires only when singleUpload = false
                 * @param {Ext.ux.FileUploader} this
                 * @param {Ext.data.Record} record upload of which has finished
                 */
                ,'filefinished'
                /**
                 * @event progress
                 * Fires when progress has been updated
                 * @param {Ext.ux.FileUploader} this
                 * @param {Object} data Progress data object
                 * @param {Ext.data.Record} record Only if singleUpload = false
                 */
                ,'progress'
        );
        // }}}

}; // eo constructor

Ext.extend(Ext.ux.FileUploader, Ext.util.Observable, {
        
        // configuration options
        // {{{
        /**
         * @cfg {Object} baseParams baseParams are sent to server in each request.
         */
         baseParams:{cmd:'upload',dir:'.'}

        /**
         * @cfg {Boolean} concurrent true to start all requests upon upload start, false to start
         * the next request only if previous one has been completed (or failed). Applicable only if
         * singleUpload = false
         */
        ,concurrent:true

        /**
         * @cfg {Boolean} enableProgress true to enable querying server for progress information
         */
        ,enableProgress:true

        /**
         * @cfg {String} jsonErrorText Text to use for json error
         */
        ,jsonErrorText:'Cannot decode JSON object'

        /**
         * @cfg {Number} Maximum client file size in bytes
         */
        ,maxFileSize:524288

        /**
         * @cfg {String} progressIdName Name to give hidden field for upload progress identificator
         */
        ,progressIdName:'UPLOAD_IDENTIFIER'

        /**
         * @cfg {Number} progressInterval How often (in ms) is progress requested from server
         */
        ,progressInterval:2000

        /**
         * @cfg {String} progressUrl URL to request upload progress from
         */
        ,progressUrl:'progress.php'

        /**
         * @cfg {Object} progressMap Mapping of received progress fields to store progress fields
         */
        ,progressMap:{
                 bytes_total:'bytesTotal'
                ,bytes_uploaded:'bytesUploaded'
                ,est_sec:'estSec'
                ,files_uploaded:'filesUploaded'
                ,speed_average:'speedAverage'
                ,speed_last:'speedLast'
                ,time_last:'timeLast'
                ,time_start:'timeStart'
        }
        /**
         * @cfg {Boolean} singleUpload true to upload files in one form, false to upload one by one
         */
        ,singleUpload:false
        
        /**
         * @cfg {Ext.data.Store} store Mandatory. Store that holds files to upload
         */

        /**
         * @cfg {String} unknownErrorText Text to use for unknow error
         */
        ,unknownErrorText:'Unknown error'

        /**
         * @cfg {String} url Mandatory. URL to upload to
         */

        // }}}

        // private
        // {{{
        /**
         * uploads in progress count
         * @private
         */
        ,upCount:0
        // }}}

        // methods
        // {{{
        /**
         * creates form to use for upload.
         * @private
         * @return {Ext.Element} form
         */
        ,createForm:function(record) {
                var progressId = parseInt(Math.random() * 1e10, 10);
                var form = Ext.getBody().createChild({
                         tag:'form'
                        ,action:this.url
                        ,method:'post'
                        ,cls:'x-hidden'
                        ,id:Ext.id()
                        ,cn:[{
                                 tag:'input'
                                ,type:'hidden'
                                ,name:'APC_UPLOAD_PROGRESS'
                                ,value:progressId
                        },{
                                 tag:'input'
                                ,type:'hidden'
                                ,name:this.progressIdName
                                ,value:progressId
                        },{
                                 tag:'input'
                                ,type:'hidden'
                                ,name:'MAX_FILE_SIZE'
                                ,value:this.maxFileSize
                        }]
                });
                if(record) {
                        record.set('form', form);
                        record.set('progressId', progressId);
                }
                else {
                        this.progressId = progressId;
                }
                return form;

        } // eo function createForm
        // }}}
        // {{{
        ,deleteForm:function(form, record) {
                form.remove();
                if(record) {
                        record.set('form', null);
                }
        } // eo function deleteForm
        // }}}
        // {{{
        /**
         * Fires event(s) on upload finish/error
         * @private
         */
        ,fireFinishEvents:function(options) {
                if(true !== this.eventsSuspended && !this.singleUpload) {
                        this.fireEvent('filefinished', this, options && options.record);
                }
                if(true !== this.eventsSuspended && 0 === this.upCount) {
                        this.stopProgress();
                        this.fireEvent('allfinished', this);
                }
        } // eo function fireFinishEvents
        // }}}
        // {{{
        /**
         * Geg the iframe identified by record
         * @private
         * @param {Ext.data.Record} record
         * @return {Ext.Element} iframe or null if not found
         */
        ,getIframe:function(record) {
                var iframe = null;
                var form = record.get('form');
                if(form && form.dom && form.dom.target) {
                        iframe = Ext.get(form.dom.target);
                }
                return iframe;
        } // eo function getIframe
        // }}}
        // {{{
        /**
         * returns options for Ajax upload request
         * @private
         * @param {Ext.data.Record} record
         * @param {Object} params params to add
         */
        ,getOptions:function(record, params) {
                var o = {
                         url:this.url
                        ,method:'post'
                        ,isUpload:true
                        ,scope:this
                        ,callback:this.uploadCallback
                        ,record:record
                        ,params:this.getParams(record, params)
                };
                return o;
        } // eo function getOptions
        // }}}
        // {{{
        /**
         * get params to use for request
         * @private
         * @return {Object} params
         */
        ,getParams:function(record, params) {
                var p = {path:this.path};
                Ext.apply(p, this.baseParams || {}, params || {});
                return p;
        }
        // }}}
        // {{{
        /**
         * processes success response
         * @private
         * @param {Object} options options the request was called with
         * @param {Object} response request response object
         * @param {Object} o decoded response.responseText
         */
        ,processSuccess:function(options, response, o) {
                var record = false;

                // all files uploadded ok
                if(this.singleUpload) {
                        this.store.each(function(r) {
                                r.set('state', 'done');
                                r.set('error', '');
                                r.commit();
                        });
                }
                else {
                        record = options.record;
                        record.set('state', 'done');
                        record.set('error', '');
                        record.commit();
                }

                this.deleteForm(options.form, record);

        } // eo processSuccess
        // }}}
        // {{{
        /**
         * processes failure response
         * @private
         * @param {Object} options options the request was called with
         * @param {Object} response request response object
         * @param {String/Object} error Error text or JSON decoded object. Optional.
         */
        ,processFailure:function(options, response, error) {
                var record = options.record;
                var records;

                // singleUpload - all files uploaded in one form
                if(this.singleUpload) {
                        // some files may have been successful
                        records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
                        records.each(function(record) {
                                var e = error.errors ? error.errors[record.id] : this.unknownErrorText;
                                if(e) {
                                        record.set('state', 'failed');
                                        record.set('error', e);
                                        Ext.getBody().appendChild(record.get('input'));
                                }
                                else {
                                        record.set('state', 'done');
                                        record.set('error', '');
                                }
                                record.commit();
                        }, this);

                        this.deleteForm(options.form);
                }
                // multipleUpload - each file uploaded in it's own form
                else {
                        if(error && 'object' === Ext.type(error)) {
                                record.set('error', error.errors && error.errors[record.id] ? error.errors[record.id] : this.unknownErrorText);
                        }
                        else if(error) {
                                record.set('error', error);
                        }
                        else if(response && response.responseText) {
                                record.set('error', response.responseText);
                        }
                        else {
                                record.set('error', this.unknownErrorText);
                        }
                        record.set('state', 'failed');
                        record.commit();
                }
        } // eof processFailure
        // }}}
        // {{{
        /**
         * Delayed task callback
         */
        ,requestProgress:function() {
                var records, p;
                var o = {
                         url:this.progressUrl
                        ,method:'post'
                        ,params:{}
                        ,scope:this
                        ,callback:function(options, success, response) {
                                var o;
                                if(true !== success) {
                                        return;
                                }
                                try {
                                        o = Ext.decode(response.responseText);
                                }
                                catch(e) {
                                        return;
                                }
                                if('object' !== Ext.type(o) || true !== o.success) {
                                        return;
                                }

                                if(this.singleUpload) {
                                        this.progress = {};
                                        for(p in o) {
                                                if(this.progressMap[p]) {
                                                        this.progress[this.progressMap[p]] = parseInt(o[p], 10);
                                                }
                                        }
                                        if(true !== this.eventsSuspended) {
                                                this.fireEvent('progress', this, this.progress);
                                        }

                                }
                                else {
                                        for(p in o) {
                                                if(this.progressMap[p] && options.record) {
                                                        options.record.set(this.progressMap[p], parseInt(o[p], 10));
                                                }
                                        }
                                        if(options.record) {
                                                options.record.commit();
                                                if(true !== this.eventsSuspended) {
                                                        this.fireEvent('progress', this, options.record.data, options.record);
                                                }
                                        }
                                }
                                this.progressTask.delay(this.progressInterval);
                        }
                };
                if(this.singleUpload) {
                        o.params[this.progressIdName] = this.progressId;
                        o.params.APC_UPLOAD_PROGRESS = this.progressId;
                        Ext.Ajax.request(o);
                }
                else {
                        records = this.store.query('state', 'uploading');
                        records.each(function(r) {
                                o.params[this.progressIdName] = r.get('progressId');
                                o.params.APC_UPLOAD_PROGRESS = o.params[this.progressIdName];
                                o.record = r;
                                (function() {
                                        Ext.Ajax.request(o);
                                }).defer(250);
                        }, this);
                }
        } // eo function requestProgress
        // }}}
        // {{{
        /**
         * path setter
         * @private
         */
        ,setPath:function(path) {
                this.path = path;
        } // eo setPath
        // }}}
        // {{{
        /**
         * url setter
         * @private
         */
        ,setUrl:function(url) {
                this.url = url;
        } // eo setUrl
        // }}}
        // {{{
        /**
         * Starts progress fetching from server
         * @private
         */
        ,startProgress:function() {
                if(!this.progressTask) {
                        this.progressTask = new Ext.util.DelayedTask(this.requestProgress, this);
                }
                this.progressTask.delay.defer(this.progressInterval / 2, this.progressTask, [this.progressInterval]);
        } // eo function startProgress
        // }}}
        // {{{
        /**
         * Stops progress fetching from server
         * @private
         */
        ,stopProgress:function() {
                if(this.progressTask) {
                        this.progressTask.cancel();
                }
        } // eo function stopProgress
        // }}}
        // {{{
        /**
         * Stops all currently running uploads
         */
        ,stopAll:function() {
                var records = this.store.query('state', 'uploading');
                records.each(this.stopUpload, this);
        } // eo function stopAll
        // }}}
        // {{{
        /**
         * Stops currently running upload
         * @param {Ext.data.Record} record Optional, if not set singleUpload = true is assumed
         * and the global stop is initiated
         */
        ,stopUpload:function(record) {
                // single abord
                var iframe = false;
                if(record) {
                        iframe = this.getIframe(record);
                        this.stopIframe(iframe);
                        this.upCount--;
                        this.upCount = 0 > this.upCount ? 0 : this.upCount;
                        record.set('state', 'stopped');
                        this.fireFinishEvents({record:record});
                }
                // all abort
                else if(this.form) {
                        iframe = Ext.fly(this.form.dom.target);
                        this.stopIframe(iframe);
                        this.upCount = 0;
                        this.fireFinishEvents();
                }

        } // eo function abortUpload
        // }}}
        // {{{
        /**
         * Stops uploading in hidden iframe
         * @private
         * @param {Ext.Element} iframe
         */
        ,stopIframe:function(iframe) {
                if(iframe) {
                        try {
                                iframe.dom.contentWindow.stop();
                                iframe.remove.defer(250, iframe);
                        }
                        catch(e){}
                }
        } // eo function stopIframe
        // }}}
        // {{{
        /**
         * Main public interface function. Preforms the upload
         */
        ,upload:function() {
                
                var records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
                if(!records.getCount()) {
                        return;
                }

                // fire beforeallstart event
                if(true !== this.eventsSuspended && false === this.fireEvent('beforeallstart', this)) {
                        return;
                }
                if(this.singleUpload) {
                        this.uploadSingle();
                }
                else {
                        records.each(this.uploadFile, this);
                }
                
                if(true === this.enableProgress) {
                        this.startProgress();
                }

        } // eo function upload
        // }}}
        // {{{
        /**
         * called for both success and failure. Does nearly nothing
         * @private
         * but dispatches processing to processSuccess and processFailure functions
         */
        ,uploadCallback:function(options, success, response) {

                var o;
                this.upCount--;
                this.form = false;

                // process ajax success
                if(true === success) {
                        try {
                                o = Ext.decode(response.responseText);
                        }
                        catch(e) {
                                this.processFailure(options, response, this.jsonErrorText);
                                this.fireFinishEvents(options);
                                return;
                        }
                        // process command success
                        if(true === o.success) {
                                this.processSuccess(options, response, o);
                        }
                        // process command failure
                        else {
                                this.processFailure(options, response, o);
                        }
                }
                // process ajax failure
                else {
                        this.processFailure(options, response);
                }

                this.fireFinishEvents(options);

        } // eo function uploadCallback
        // }}}
        // {{{
        /**
         * Uploads one file
         * @param {Ext.data.Record} record
         * @param {Object} params Optional. Additional params to use in request.
         */
        ,uploadFile:function(record, params) {
                // fire beforestart event
                if(true !== this.eventsSuspended && false === this.fireEvent('beforefilestart', this, record)) {
                        return;
                }

                // create form for upload
                var form = this.createForm(record);

                // append input to the form
                var inp = record.get('input');
                inp.set({name:inp.id});
                form.appendChild(inp);

                // get params for request
                var o = this.getOptions(record, params);
                o.form = form;

                // set state 
                record.set('state', 'uploading');
                record.set('pctComplete', 0);

                // increment active uploads count
                this.upCount++;

                // request upload
                Ext.Ajax.request(o);

                // todo:delete after devel
                this.getIframe.defer(100, this, [record]);

        } // eo function uploadFile
        // }}}
        // {{{
        /**
         * Uploads all files in single request
         */
        ,uploadSingle:function() {

                // get records to upload
                var records = this.store.queryBy(function(r){return 'done' !== r.get('state');});
                if(!records.getCount()) {
                        return;
                }

                // create form and append inputs to it
                var form = this.createForm();
                records.each(function(record) {
                        var inp = record.get('input');
                        inp.set({name:inp.id});
                        form.appendChild(inp);
                        record.set('state', 'uploading');
                }, this);

                // create options for request
                var o = this.getOptions();
                o.form = form;

                // save form for stop
                this.form = form;

                // increment active uploads counter
                this.upCount++;

                // request upload
                Ext.Ajax.request(o);
        
        } // eo function uploadSingle
        // }}}

}); // eo extend

// register xtype
Ext.reg('fileuploader', Ext.ux.FileUploader);

 // eof