Source of
js/Ext.ux.form.BrowseButton.js:
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
;
this
.
originalScope
=
this
.
scope
;
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
);
}
// Cover cases where someone tabs to the button:
// Listen to focus of the button so we can translate the focus to the input file el.
var
buttonEl
=
this
.
el
.
child
(
this
.
buttonSelector
);
buttonEl
.
on
(
'focus'
,
this
.
onButtonFocus
,
this
);
// In IE, it's possible to tab to the text portion of the input file el.
// We want to listen to keyevents so that if a space is pressed, we "click" the input file el.
if
(
Ext
.
isIE
) {
this
.
el
.
on
(
'keydown'
,
this
.
onButtonKeyDown
,
this
);
}
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
();
// The button container can have a width and height of zero when it's rendered in a hidden panel.
// This is most noticable when using a card layout, as the items are all rendered but hidden,
// (unless deferredRender is set to true).
// In this case, the clip size can't be determined, so we attempt to set it later.
// This check repeats until the button container has a size.
if
(
width
===
0
||
height
===
0
) {
this
.
setClipSize
.
defer
(
100
,
this
);
}
else
{
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
(){
// When an input file gets detached and set as the child of a different DOM element,
// straggling <em> elements get left behind.
// I don't know why this happens but we delete any <em> elements we can find under the floatEl to prevent a memory leak.
this
.
floatEl
.
select
(
'em'
).
each
(
function
(
el
){
el
.
remove
();
});
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
),
tabindex
:
this
.
tabIndex
,
// 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
,
'focus'
:
this
.
onInputFileFocus
,
'select'
:
this
.
onInputFileFocus
,
'blur'
:
this
.
onInputFileBlur
,
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
;
}
}
},
/**
* Redirecting focus to the input file element so the user can press space and select files.
* @param {Event} e focus event.
* @private
*/
onButtonFocus
:
function
(
e
){
if
(
this
.
inputFileEl
) {
this
.
inputFileEl
.
focus
();
e
.
stopEvent
();
}
},
/**
* Handler for the IE case where once can tab to the text box of an input file el.
* If the key is a space, we simply "click" the inputFileEl.
* @param {Event} e key event.
* @private
*/
onButtonKeyDown
:
function
(
e
){
if
(
this
.
inputFileEl
&&
e
.
getKey
() ==
Ext
.
EventObject
.
SPACE
) {
this
.
inputFileEl
.
dom
.
click
();
e
.
stopEvent
();
}
},
/**
* 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
);
},
/**
* Add the visual enhancement to the button when the input file recieves focus.
* This is the tip for the user that now he/she can press space to select the file.
* @private
*/
onInputFileFocus
:
function
(
e
){
if
(!
this
.
isDisabled
) {
this
.
el
.
addClass
(
"x-btn-over"
);
}
},
/**
* Removes the visual enhancement from the button.
* @private
*/
onInputFileBlur
:
function
(
e
){
this
.
el
.
removeClass
(
"x-btn-over"
);
},
/**
* 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
);
Free web analytics, website statistics