Subversion Repositories eFlore/Applications.cel

Rev

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

Ext.namespace('Ext.ux.form');

/**
 * @class Ext.ux.form.BrowseButton
 * @extends Ext.Button
 * Ext.Button that provides a customizable file browse button.
 * Clicking this button, pops up a file dialog box for a user to select the file to upload.
 * This is accomplished by having a transparent <input type="file"> box above the Ext.Button.
 * When a user thinks he or she is clicking the Ext.Button, they're actually clicking the hidden input "Browse..." box.
 * Note: this class can be instantiated explicitly or with xtypes anywhere a regular Ext.Button can be except in 2 scenarios:
 * - Panel.addButton method both as an instantiated object or as an xtype config object.
 * - Panel.buttons config object as an xtype config object.
 * These scenarios fail because Ext explicitly creates an Ext.Button in these cases.
 * Browser compatibility:
 * Internet Explorer 6:
 * - no issues
 * Internet Explorer 7:
 * - no issues
 * Firefox 2 - Windows:
 * - pointer cursor doesn't display when hovering over the button.
 * Safari 3 - Windows:
 * - no issues.
 * @author loeppky - based on the work done by MaximGB in Ext.ux.UploadDialog (http://extjs.com/forum/showthread.php?t=21558)
 * The follow the curosr float div idea also came from MaximGB.
 * @see http://extjs.com/forum/showthread.php?t=29032
 * @constructor
 * Create a new BrowseButton.
 * @param {Object} config Configuration options
 */
Ext.ux.form.BrowseButton = Ext.extend(Ext.Button, {
        /*
         * Config options:
         */
        /**
         * @cfg {String} inputFileName
         * Name to use for the hidden input file DOM element.  Deaults to "file".
         */
        inputFileName: 'file',
        /**
         * @cfg {Boolean} debug
         * Toggle for turning on debug mode.
         * Debug mode doesn't make clipEl transparent so that one can see how effectively it covers the Ext.Button.
         * In addition, clipEl is given a green background and floatEl a red background to see how well they are positioned.
         */
        debug: false,
        
        
        /*
         * Private constants:
         */
        /**
         * @property FLOAT_EL_WIDTH
         * @type Number
         * The width (in pixels) of floatEl.
         * It should be less than the width of the IE "Browse" button's width (65 pixels), since IE doesn't let you resize it.
         * We define this width so we can quickly center floatEl at the mouse cursor without having to make any function calls.
         * @private
         */
        FLOAT_EL_WIDTH: 60,
        
        /**
         * @property FLOAT_EL_HEIGHT
         * @type Number
         * The heigh (in pixels) of floatEl.
         * It should be less than the height of the "Browse" button's height.
         * We define this height so we can quickly center floatEl at the mouse cursor without having to make any function calls.
         * @private
         */
        FLOAT_EL_HEIGHT: 18,
        
        
        /*
         * Private properties:
         */
        /**
         * @property buttonCt
         * @type Ext.Element
         * Element that contains the actual Button DOM element.
         * We store a reference to it, so we can easily grab its size for sizing the clipEl.
         * @private
         */
        buttonCt: null,
        /**
         * @property clipEl
         * @type Ext.Element
         * Element that contains the floatEl.
         * This element is positioned to fill the area of Ext.Button and has overflow turned off.
         * This keeps floadEl tight to the Ext.Button, and prevents it from masking surrounding elements.
         * @private
         */
        clipEl: null,
        /**
         * @property floatEl
         * @type Ext.Element
         * Element that contains the inputFileEl.
         * This element is size to be less than or equal to the size of the input file "Browse" button.
         * It is then positioned wherever the user moves the cursor, so that their click always clicks the input file "Browse" button.
         * Overflow is turned off to preven inputFileEl from masking surrounding elements.
         * @private
         */
        floatEl: null,
        /**
         * @property inputFileEl
         * @type Ext.Element
         * Element for the hiden file input.
         * @private
         */
        inputFileEl: null,
        /**
         * @property originalHandler
         * @type Function
         * The handler originally defined for the Ext.Button during construction using the "handler" config option.
         * We need to null out the "handler" property so that it is only called when a file is selected.
         * @private
         */
        originalHandler: null,
        /**
         * @property originalScope
         * @type Object
         * The scope originally defined for the Ext.Button during construction using the "scope" config option.
         * While the "scope" property doesn't need to be nulled, to be consistent with originalHandler, we do.
         * @private
         */
        originalScope: null,
        
        
        /*
         * Protected Ext.Button overrides
         */
        /**
         * @see Ext.Button.initComponent
         */
        initComponent: function(){
                Ext.ux.form.BrowseButton.superclass.initComponent.call(this);
                // Store references to the original handler and scope before nulling them.
                // This is done so that this class can control when the handler is called.
                // There are some cases where the hidden file input browse button doesn't completely cover the Ext.Button.
                // The handler shouldn't be called in these cases.  It should only be called if a new file is selected on the file system.  
                this.originalHandler = this.handler || null;
                this.originalScope = this.scope || window;
                this.handler = null;
                this.scope = null;
        },
        
        /**
         * @see Ext.Button.onRender
         */
        onRender: function(ct, position){
                Ext.ux.form.BrowseButton.superclass.onRender.call(this, ct, position); // render the Ext.Button
                this.buttonCt = this.el.child('.x-btn-center em');
                this.buttonCt.position('relative'); // this is important!
                var styleCfg = {
                        position: 'absolute',
                        overflow: 'hidden',
                        top: '0px', // default
                        left: '0px' // default
                };
                // browser specifics for better overlay tightness
                if (Ext.isIE) {
                        Ext.apply(styleCfg, {
                                left: '-3px',
                                top: '-3px'
                        });
                } else if (Ext.isGecko) {
                        Ext.apply(styleCfg, {
                                left: '-3px',
                                top: '-3px'
                        });
                } else if (Ext.isSafari) {
                        Ext.apply(styleCfg, {
                                left: '-4px',
                                top: '-2px'
                        });
                }
                this.clipEl = this.buttonCt.createChild({
                        tag: 'div',
                        style: styleCfg
                });
                this.setClipSize();
                this.clipEl.on({
                        'mousemove': this.onButtonMouseMove,
                        'mouseover': this.onButtonMouseMove,
                        scope: this
                });
                
                this.floatEl = this.clipEl.createChild({
                        tag: 'div',
                        style: {
                                position: 'absolute',
                                width: this.FLOAT_EL_WIDTH + 'px',
                                height: this.FLOAT_EL_HEIGHT + 'px',
                                overflow: 'hidden'
                        }
                });
                
                
                if (this.debug) {
                        this.clipEl.applyStyles({
                                'background-color': 'green'
                        });
                        this.floatEl.applyStyles({
                                'background-color': 'red'
                        });
                } else {
                        this.clipEl.setOpacity(0.0);
                }
                
                this.createInputFile();
        },
        
        
        /*
         * Private helper methods:
         */
        /**
         * Sets the size of clipEl so that is covering as much of the button as possible.
         * @private
         */
        setClipSize: function(){
                if (this.clipEl) {
                        var width = this.buttonCt.getWidth();
                        var height = this.buttonCt.getHeight();
                        if (Ext.isIE) {
                                width = width + 5;
                                height = height + 5;
                        } else if (Ext.isGecko) {
                                width = width + 6;
                                height = height + 6;
                        } else if (Ext.isSafari) {
                                width = width + 6;
                                height = height + 6;
                        }
                        this.clipEl.setSize(width, height);
                }
        },
        
        /**
         * Creates the input file element and adds it to inputFileCt.
         * The created input file elementis sized, positioned, and styled appropriately.
         * Event handlers for the element are set up, and a tooltip is applied if defined in the original config.
         * @private
         */
        createInputFile: function(){
        
                this.inputFileEl = this.floatEl.createChild({
                        tag: 'input',
                        type: 'file',
                        size: 1, // must be > 0. It's value doesn't really matter due to our masking div (inputFileCt).  
                        name: this.inputFileName || Ext.id(this.el),
                        // Use the same pointer as an Ext.Button would use.  This doesn't work in Firefox.
                        // This positioning right-aligns the input file to ensure that the "Browse" button is visible.
                        style: {
                                position: 'absolute',
                                cursor: 'pointer',
                                right: '0px',
                                top: '0px'
                        }
                });
                this.inputFileEl = this.inputFileEl.child('input') || this.inputFileEl;
                
                // setup events
                this.inputFileEl.on({
                        'click': this.onInputFileClick,
                        'change': this.onInputFileChange,
                        scope: this
                });
                
                // add a tooltip
                if (this.tooltip) {
                        if (typeof this.tooltip == 'object') {
                                Ext.QuickTips.register(Ext.apply({
                                        target: this.inputFileEl
                                }, this.tooltip));
                        } else {
                                this.inputFileEl.dom[this.tooltipType] = this.tooltip;
                        }
                }
        },
        
        /**
         * Handler when the cursor moves over the clipEl.
         * The floatEl gets centered to the cursor location.
         * @param {Event} e mouse event.
         * @private
         */
        onButtonMouseMove: function(e){
                var xy = e.getXY();
                xy[0] -= this.FLOAT_EL_WIDTH / 2;
                xy[1] -= this.FLOAT_EL_HEIGHT / 2;
                this.floatEl.setXY(xy);
        },
        
        /**
         * Handler when inputFileEl's "Browse..." button is clicked.
         * @param {Event} e click event.
         * @private
         */
        onInputFileClick: function(e){
                e.stopPropagation();
        },
        
        /**
         * Handler when inputFileEl changes value (i.e. a new file is selected).
         * @private
         */
        onInputFileChange: function(){
                if (this.originalHandler) {
                        this.originalHandler.call(this.originalScope, this);
                }
        },
        
        
        /*
         * Public methods:
         */
        /**
         * Detaches the input file associated with this BrowseButton so that it can be used for other purposed (e.g. uplaoding).
         * The returned input file has all listeners and tooltips applied to it by this class removed.
         * @param {Boolean} whether to create a new input file element for this BrowseButton after detaching.
         * True will prevent creation.  Defaults to false.
         * @return {Ext.Element} the detached input file element.
         */
        detachInputFile: function(noCreate){
                var result = this.inputFileEl;
                
                if (typeof this.tooltip == 'object') {
                        Ext.QuickTips.unregister(this.inputFileEl);
                } else {
                        this.inputFileEl.dom[this.tooltipType] = null;
                }
                this.inputFileEl.removeAllListeners();
                this.inputFileEl = null;
                
                if (!noCreate) {
                        this.createInputFile();
                }
                return result;
        },
        
        /**
         * @return {Ext.Element} the input file element attached to this BrowseButton.
         */
        getInputFile: function(){
                return this.inputFileEl;
        },
        
        /**
         * @see Ext.Button.disable
         */
        disable: function(){
                Ext.ux.form.BrowseButton.superclass.disable.call(this);
                this.inputFileEl.dom.disabled = true;
        },
        
        /**
         * @see Ext.Button.enable
         */
        enable: function(){
                Ext.ux.form.BrowseButton.superclass.enable.call(this);
                this.inputFileEl.dom.disabled = false;
        }
});

Ext.reg('browsebutton', Ext.ux.form.BrowseButton);