icloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsapp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
11 KiB
349 lines
11 KiB
9 years ago
|
/**
|
||
|
* A file upload field which has custom styling and allows control over the button text and other
|
||
|
* features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.
|
||
|
* It uses a hidden file input element behind the scenes to allow user selection of a file and to
|
||
|
* perform the actual upload during {@link Ext.form.Basic#submit form submit}.
|
||
|
*
|
||
|
* Because there is no secure cross-browser way to programmatically set the value of a file input,
|
||
|
* the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return
|
||
|
* a value that is browser-dependent; some have just the file name, some have a full path, some use
|
||
|
* a fake path.
|
||
|
*
|
||
|
* **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for
|
||
|
* {@link Ext.form.Basic#hasUpload} for details.
|
||
|
*
|
||
|
* # Example Usage
|
||
|
*
|
||
|
* @example
|
||
|
* Ext.create('Ext.form.Panel', {
|
||
|
* title: 'Upload a Photo',
|
||
|
* width: 400,
|
||
|
* bodyPadding: 10,
|
||
|
* frame: true,
|
||
|
* renderTo: Ext.getBody(),
|
||
|
* items: [{
|
||
|
* xtype: 'filefield',
|
||
|
* name: 'photo',
|
||
|
* fieldLabel: 'Photo',
|
||
|
* labelWidth: 50,
|
||
|
* msgTarget: 'side',
|
||
|
* allowBlank: false,
|
||
|
* anchor: '100%',
|
||
|
* buttonText: 'Select Photo...'
|
||
|
* }],
|
||
|
*
|
||
|
* buttons: [{
|
||
|
* text: 'Upload',
|
||
|
* handler: function() {
|
||
|
* var form = this.up('form').getForm();
|
||
|
* if(form.isValid()){
|
||
|
* form.submit({
|
||
|
* url: 'photo-upload.php',
|
||
|
* waitMsg: 'Uploading your photo...',
|
||
|
* success: function(fp, o) {
|
||
|
* Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');
|
||
|
* }
|
||
|
* });
|
||
|
* }
|
||
|
* }
|
||
|
* }]
|
||
|
* });
|
||
|
*/
|
||
|
Ext.define('Ext.form.field.File', {
|
||
|
extend: 'Ext.form.field.Text',
|
||
|
alias: ['widget.filefield', 'widget.fileuploadfield'],
|
||
|
alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],
|
||
|
requires: [
|
||
|
'Ext.form.field.FileButton',
|
||
|
'Ext.form.trigger.Component'
|
||
|
],
|
||
|
|
||
|
needArrowKeys: false,
|
||
|
|
||
|
triggers: {
|
||
|
filebutton: {
|
||
|
type: 'component',
|
||
|
hideOnReadOnly: false,
|
||
|
// Most form fields prevent the default browser action on mousedown of the trigger.
|
||
|
// This is intended to prevent the field's input element from losing focus when
|
||
|
// the trigger is clicked. File fields disable this behavior because:
|
||
|
// 1. The input element does not receive focus when the field is focused. The button does.
|
||
|
// 2. Preventing the default action of touchstart (translated from mousedown
|
||
|
// on mobile browsers) prevents the browser's file dialog from opening.
|
||
|
preventMouseDown: false
|
||
|
}
|
||
|
},
|
||
|
|
||
|
//<locale>
|
||
|
/**
|
||
|
* @cfg {String} buttonText
|
||
|
* The button text to display on the upload button. Note that if you supply a value for
|
||
|
* {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.
|
||
|
*/
|
||
|
buttonText: 'Browse...',
|
||
|
//</locale>
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} buttonOnly
|
||
|
* True to display the file upload field as a button with no visible text field. If true, all
|
||
|
* inherited Text members will still be available.
|
||
|
*/
|
||
|
buttonOnly: false,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Number} buttonMargin
|
||
|
* The number of pixels of space reserved between the button and the text field. Note that this only
|
||
|
* applies if {@link #buttonOnly} = false.
|
||
|
*/
|
||
|
buttonMargin: 3,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} clearOnSubmit
|
||
|
* True to clear the selected file value when the form this field belongs to
|
||
|
* is submitted to the server.
|
||
|
*/
|
||
|
clearOnSubmit: true,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Object} buttonConfig
|
||
|
* Specify optional custom button {@link Ext.button.Button} config (eg. iconCls, text) for the upload button
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @event change
|
||
|
* Fires when the underlying file input field's value has changed from the user selecting a new file from the system
|
||
|
* file selection dialog.
|
||
|
* @param {Ext.ux.form.FileUploadField} this
|
||
|
* @param {String} value The file value returned by the underlying file input field
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @property {Ext.dom.Element} fileInputEl
|
||
|
* A reference to the invisible file input element created for this upload field. Only populated after this
|
||
|
* component is rendered.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @property {Ext.button.Button} button
|
||
|
* A reference to the trigger Button component created for this upload field. Only populated after this component is
|
||
|
* rendered.
|
||
|
*/
|
||
|
|
||
|
|
||
|
// private
|
||
|
extraFieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',
|
||
|
// private
|
||
|
inputCls: Ext.baseCSSPrefix + 'form-text-file',
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} [readOnly=true]
|
||
|
* Unlike with other form fields, the readOnly config defaults to true in File field.
|
||
|
*/
|
||
|
readOnly: true,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Boolean} editable
|
||
|
* @inheritdoc
|
||
|
*/
|
||
|
editable: false,
|
||
|
|
||
|
submitValue: false,
|
||
|
|
||
|
/**
|
||
|
* Do not show hand pointer over text field since file choose dialog is only shown when clicking in the button
|
||
|
* @private
|
||
|
*/
|
||
|
triggerNoEditCls: '',
|
||
|
|
||
|
// @private
|
||
|
// Extract the file element, button outer element, and button active element.
|
||
|
childEls: ['browseButtonWrap'],
|
||
|
|
||
|
// @private
|
||
|
applyTriggers: function(triggers) {
|
||
|
var me = this,
|
||
|
triggerCfg = (triggers || {}).filebutton;
|
||
|
|
||
|
if (triggerCfg) {
|
||
|
triggerCfg.component = Ext.apply({
|
||
|
xtype: 'filebutton',
|
||
|
ownerCt: me,
|
||
|
id: me.id + '-button',
|
||
|
ui: me.ui,
|
||
|
disabled: me.disabled,
|
||
|
text: me.buttonText,
|
||
|
style: me.buttonOnly ? '' : me.getButtonMarginProp() + me.buttonMargin + 'px',
|
||
|
inputName: me.getName(),
|
||
|
listeners: {
|
||
|
scope: me,
|
||
|
change: me.onFileChange
|
||
|
}
|
||
|
}, me.buttonConfig);
|
||
|
|
||
|
return me.callParent([triggers]);
|
||
|
}
|
||
|
// <debug>
|
||
|
else {
|
||
|
Ext.Error.raise(me.$className + ' requires a valid trigger config containing "filebutton" specification');
|
||
|
}
|
||
|
// </debug>
|
||
|
},
|
||
|
|
||
|
getSubTplData: function(fieldData) {
|
||
|
var data = this.callParent([fieldData]);
|
||
|
|
||
|
// Input field itself should not be focusable since it's always decorative;
|
||
|
// however the input element is naturally focusable (and tabbable) so we have to
|
||
|
// deactivate it by setting its tabIndex to -1.
|
||
|
data.tabIdx = -1;
|
||
|
|
||
|
return data;
|
||
|
},
|
||
|
|
||
|
// @private
|
||
|
onRender: function() {
|
||
|
var me = this,
|
||
|
inputEl, button, buttonEl, trigger;
|
||
|
|
||
|
me.callParent(arguments);
|
||
|
|
||
|
inputEl = me.inputEl;
|
||
|
//name goes on the fileInput, not the text input
|
||
|
inputEl.dom.name = '';
|
||
|
// Some browsers will show a blinking cursor in the field, even if it's readonly. If we do happen
|
||
|
// to receive focus, forward it on to our focusEl. Also note that in IE, the file input is treated as
|
||
|
// 2 elements for tabbing purposes (the text, then the button). So as you tab through, it will take 2
|
||
|
// tabs to get to the next field. As far as I know there's no way around this in any kind of reasonable way.
|
||
|
inputEl.on('focus', me.focus, me);
|
||
|
|
||
|
trigger = me.getTrigger('filebutton');
|
||
|
button = me.button = trigger.component;
|
||
|
me.fileInputEl = button.fileInputEl;
|
||
|
buttonEl = button.el;
|
||
|
|
||
|
if (me.buttonOnly) {
|
||
|
me.inputWrap.setDisplayed(false);
|
||
|
me.shrinkWrap = 3;
|
||
|
}
|
||
|
|
||
|
// Ensure the trigger element is sized correctly upon render
|
||
|
trigger.el.setWidth(buttonEl.getWidth() + buttonEl.getMargin('lr'));
|
||
|
if (Ext.isIE) {
|
||
|
me.button.getEl().repaint();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Gets the markup to be inserted into the subTplMarkup.
|
||
|
*/
|
||
|
getTriggerMarkup: function() {
|
||
|
return '<td id="' + this.id + '-browseButtonWrap" data-ref="browseButtonWrap" role="presentation"></td>';
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @private Event handler fired when the user selects a file.
|
||
|
*/
|
||
|
onFileChange: function(button, e, value) {
|
||
|
this.duringFileSelect = true;
|
||
|
Ext.form.field.File.superclass.setValue.call(this, value);
|
||
|
delete this.duringFileSelect;
|
||
|
},
|
||
|
|
||
|
didValueChange: function(){
|
||
|
// In the case of the file field, the change event will only ever fire
|
||
|
// if the value actually changes, so we always want to fire the change event
|
||
|
// This affects Chrome specifically, because hitting the cancel button will
|
||
|
// reset the file upload.
|
||
|
return !!this.duringFileSelect;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Overridden to do nothing
|
||
|
* @method
|
||
|
*/
|
||
|
setValue: Ext.emptyFn,
|
||
|
|
||
|
reset : function(){
|
||
|
var me = this,
|
||
|
clear = me.clearOnSubmit;
|
||
|
if (me.rendered) {
|
||
|
me.button.reset(clear);
|
||
|
me.fileInputEl = me.button.fileInputEl;
|
||
|
if (clear) {
|
||
|
me.inputEl.dom.value = '';
|
||
|
// Reset the underlying value if we're clearing it
|
||
|
Ext.form.field.File.superclass.setValue.call(this, null);
|
||
|
}
|
||
|
}
|
||
|
me.callParent();
|
||
|
},
|
||
|
|
||
|
onShow: function(){
|
||
|
this.callParent();
|
||
|
// If we started out hidden, the button may have a messed up layout
|
||
|
// since we don't act like a container
|
||
|
this.button.updateLayout();
|
||
|
},
|
||
|
|
||
|
onDisable: function(){
|
||
|
this.callParent();
|
||
|
this.button.disable();
|
||
|
},
|
||
|
|
||
|
onEnable: function(){
|
||
|
this.callParent();
|
||
|
this.button.enable();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* @method
|
||
|
* @inheritdoc
|
||
|
*/
|
||
|
isFileUpload: Ext.returnTrue,
|
||
|
|
||
|
extractFileInput: function() {
|
||
|
var me = this,
|
||
|
fileInput;
|
||
|
|
||
|
if (me.rendered) {
|
||
|
fileInput = me.button.fileInputEl.dom;
|
||
|
me.reset();
|
||
|
} else {
|
||
|
// Create a fake empty field here so it will still be submitted.
|
||
|
// All other unrendered fields provide a value.
|
||
|
fileInput = document.createElement('input');
|
||
|
fileInput.type = 'file';
|
||
|
fileInput.className = Ext.baseCSSPrefix + 'hidden-display';
|
||
|
fileInput.name = me.getName();
|
||
|
}
|
||
|
return fileInput;
|
||
|
},
|
||
|
|
||
|
restoreInput: function(el) {
|
||
|
// If we're not rendered we don't need to do anything, it will be created
|
||
|
// when we get flushed to the DOM.
|
||
|
if (this.rendered) {
|
||
|
var button = this.button;
|
||
|
button.restoreInput(el);
|
||
|
this.fileInputEl = button.fileInputEl;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
onDestroy: function(){
|
||
|
this.fileInputEl = this.button = null;
|
||
|
this.callParent();
|
||
|
},
|
||
|
|
||
|
getButtonMarginProp: function() {
|
||
|
return 'margin-left:';
|
||
|
},
|
||
|
|
||
|
privates: {
|
||
|
getFocusEl: function() {
|
||
|
return this.button;
|
||
|
},
|
||
|
|
||
|
getFocusClsEl: Ext.privateFn
|
||
|
}
|
||
|
});
|