skypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmail
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.
348 lines
11 KiB
348 lines
11 KiB
/** |
|
* 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 |
|
} |
|
});
|
|
|