--- /dev/null
+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);