|
|||||||
![]() |
|
|
Thread Tools |
|
#1
|
||||
|
||||
|
Note: this extension is no longer supported (by me) because the Ext team have provided their own File Upload field (see http://extjs.com/deploy/dev/examples...le-upload.html). As a result, you'll either need to read the most recent posts where people have posted updated code to support newer browsers and Ext versions or switch to using the Ext provided control.
I have put work into fixing Ext 3.x's Ext.ux.form.FileUploadField. See http://www.extjs.com/forum/showthread.php?p=402903. It also contains updated code. I needed the ability for a user to click an Ext button and for a file browse window to appear. jsakalos has done this in his Ext.ux.UploadForm and MaximGB has done it in his Ext.ux.UploadDialog extension. I preferred MaximGB's implementation because of the slick BrowseButton class he had created to encapsulate the logic. It also maintained all of the original Ext.Button look and feel (e.g. tooltips, hovering). I have taken his original design and improved upon it in the following ways:
Updates:
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 {
// We don't set the clipEl to be transparent, because IE 6/7 occassionaly looses mouse events for transparent elements.
// We have listeners on the clipEl that can't be lost as they're needed for realligning the input file element.
this.floatEl.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);
Ext compatibility: This class can be instantiated explicitly or with xtypes anywhere a regular Ext.Button can be except in 2 scenarios:
Browser compatibility: Internet Explorer 6:
Known issues:
|
|
#2
|
||||
|
||||
|
Here’s a sample of Ext.ux.form.BrowseButton in action: http://loeppky.com/steven/code/samples/BrowseButton/
ScreenshotPanel class that uses Ext.ux.form.BrowseButton for selecting a screenshot, and then creates a form to upload it. This class highlights the different ways Ext.ux.form.BrowseButton can be used. Updates:
/**
* @class Ext.ux.ScreenshotPanel
* @extends Ext.Panel
* Renders a browse button for demonstration purposes.
* @constructor
* @param {Object} config The config object
*/
Ext.ux.ScreenshotsPanel = Ext.extend(Ext.Panel, {
/*
* Config options
*/
/**
* For whether to draw the panel (and it's BrowseButtons) in debug mode.
* @type Boolean
*/
debug: false,
/*
* Private properties
*/
/**
* Panel for displaying the files that have selected for upload.
* @type Ext.Panel
*/
filePanel: null,
/**
* Form for uploading screenshot images.
* Is only instantiated if a screenshot is selected to upload.
* @type Ext.form.BasicForm
*/
addScreenshotForm: null,
/*
* Protected methods
*/
/**
* @see Ext.Panel.initConfiguration
*/
initComponent: function(){
var browseButtonBaseConfig = {
xtype: 'browsebutton',
handler: this.onAddScreenshot,
scope: this,
tooltip: 'Click to upload a new screenshot.',
inputFileName: 'screenshot',
debug: this.debug // set to true to see the "Browse" overlay
};
this.filePanel = new Ext.Panel({
frame: true,
title: 'selected files',
buttonAlign: 'center',
buttons: [{
text: 'Upload (does not work and will fail)',
handler: this.uploadScreenshots,
scope: this
}]
});
Ext.apply(this, {
tbar: [
new Ext.ux.form.BrowseButton(Ext.apply({
text: 'tbar - explicit'
}, browseButtonBaseConfig)),
Ext.apply({
text: 'tbar - xtype'
}, browseButtonBaseConfig)
],
items: [
new Ext.ux.form.BrowseButton(Ext.apply({
text: 'items - explicit'
}, browseButtonBaseConfig)),
Ext.apply({
text: 'items - xtype'
}, browseButtonBaseConfig),
this.filePanel
],
buttons: [
new Ext.ux.form.BrowseButton(Ext.apply({
text: 'buttons - explicit'
}, browseButtonBaseConfig)),
Ext.apply({
text: 'buttons - xtype'
}, browseButtonBaseConfig) // doesn't work correctly as Ext uses the config object to instaniate an Ext.Button
]
});
Ext.ux.ScreenshotsPanel.superclass.initComponent.call(this);
},
/*
* Private methods
*/
/**
* Handler for the add screenshot button.
* @param {Ext.ux.form.BrowseButton} browseButton The browse button where "Add Screenshot" was clicked.
* @private
*/
onAddScreenshot: function(browseButton){
if (!this.addScreenshotForm) { // if the form hasn't been created
var addScreenshotFormEl = this.filePanel.body.createChild({
tag: 'form',
style: 'display:none'
});
this.addScreenshotForm = new Ext.form.BasicForm(addScreenshotFormEl, {
url: '/AddScreenshot',
fileUpload: true
});
}
var inputFileEl = browseButton.detachInputFile();
inputFileEl.appendTo(this.addScreenshotForm.getEl());
this.filePanel.body.createChild({
tag: 'div',
html: inputFileEl.dom.value
});
},
/**
* Handler for the upload screenshots button
*/
uploadScreenshots: function(){
this.addScreenshotForm.submit({
params: {
extraParam1: 'value1'
},
success: this.onAddScreenshotSuccess,
failure: this.onAddScreenshotFailure,
scope: this,
waitTitle: 'Uploading Screenshot',
waitMsg: 'Your screenshot is being uploaded...'
});
},
/**
* Callback for when a screenshot has been successfully added.
* @param {Ext.form.BasicForm} form the form for which the uploading occurred.
* @param {Ext.form.Action} action the action of the form submit
* @private
*/
onAddScreenshotSuccess: function(form, action){
// remove the file input element so that it doesn't get uploaded again with the next screenshot
var inputFileEl = this.addScreenshotForm.getEl().child('input');
inputFileEl.remove();
Ext.Msg.show({
title: 'Screenshot Upload Success',
msg: 'Your screenshot was successfully uploaded.',
buttons: Ext.Msg.OK,
minWidth: 300
});
},
/**
* Callback for when a screenshot has not been successfully added.
* @param {Ext.form.BasicForm} form the form for which the uploading occurred.
* @param {Ext.form.Action} action the action of the form submit
* @private
*/
onAddScreenshotFailure: function(form, action){
// remove the file input element so that it doesn't get uploaded again with the next screenshot
var inputFileEl = this.addScreenshotForm.getEl().child('input');
inputFileEl.remove();
var errorMessageTemplate = new Ext.XTemplate('<p>Your screenshot was not uploaded. The server reported the following errors:</p>', '<tpl for="errors">', '<p>- {.}</p>', '</tpl>');
Ext.Msg.show({
title: 'Screenshot Upload Failed',
msg: errorMessageTemplate.applyTemplate(action.result),
buttons: Ext.Msg.OK,
minWidth: 300
});
}
});
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<link rel="stylesheet" type="text/css" href="../../external/ext-2.1/resources/css/ext-all.css"/>
<!-- Ext dependencies -->
<script type="text/javascript" src="../../external/ext-2.1/adapter/ext/ext-base.js">
</script>
<script type="text/javascript" src="../../external/ext-2.1/ext-all-debug.js">
</script>
<!-- Ext extension dependencies -->
<script type="text/javascript" src="../../external/ext-extensions/source/widgets/form/BrowseButton.js">
</script>
<!-- Application dependencies -->
<script type="text/javascript" src="ScreenshotsPanel.js">
</script>
<!-- Bootstrap application -->
<script type="text/javascript">
Ext.onReady(function(){
Ext.QuickTips.init();
var viewport = new Ext.Viewport({
items: [{
xtype: 'panel',
items: [{
tag : 'div',
html : 'Form post: <a href="http://extjs.com/forum/showthread.php?t=29032">http://extjs.com/forum/showthread.php?t=29032</a>'
}, new Ext.ux.ScreenshotsPanel({
title: 'Screenshot Adder - Debug = false, meaning Browse butons are hidden.',
frame: true,
debug: false
}), new Ext.ux.ScreenshotsPanel({
title: 'Screenshot Adder - Debug = true, meaning Browse butons are visible.',
frame: true,
debug: true
})]
}]
});
});
</script>
<title>Ext.ux.form.BrowseButton sample</title>
</head>
<body>
</body>
</html>
|
|
#3
|
|||
|
|||
|
Good job.
I got an error in the destroy method. 'inputFileCt is not defined' Perhaps it needs to be changed to 'this.inputFileCt.remove(); this.inputFileCt = null' |
|
#4
|
|||
|
|||
|
Thnx a gazillion, I was just going to try do something like this for myself
![]() |
|
#5
|
||||
|
||||
|
shane.fox: I forgot to update the destroy method yesterday when I improved the code. In looking at it again and testing, I have found that the destroy method is unnecessary to override. I have removed it from the Ext.ux.form.BrowseButton class above. Let me know how that works for you.
|
|
#6
|
||||
|
||||
|
JorisA: you're very welcome although the idea credit definitely goes to MaximGB
![]() |
|
#7
|
||||
|
||||
|
I updated post #2 with a link to a sample of BrowseButton online: http://loeppky.com/steven/code/samples/BrowseButton/
|
|
#8
|
|||
|
|||
|
Hi, i use your BrowseButton very successful in a ASP.NET - project. Thank you for the good job.
If someone is interested - see my helper files in the appendix. Now for some Problems:
Greetings from the baltic, thomas |
|
#9
|
||||
|
||||
|
t34: Glad to hear it's working for you, and thanks for reporting these issues. Here's my thoughts:
|
|
#10
|
||||
|
||||
|
see pic below... both render the same
|
![]() |
| Thread Tools | |
|
|