/** * 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' ], /** * @cfg {String} [accept] An optional list of file MIME types accepted by this field. * This string will be rendered in to the `accept` attribute of the file input and should * conform to HTML requirements: http://www.w3.org/TR/html-markup/input.file.html * * @since 6.2.0 */ /** * @cfg {String} emptyText * Overridden to undefined as {@link #emptyText} is not supported with {@link #inputType inputType}:'file' and should be avoided. * The default text to place into an empty field. */ emptyText: undefined, 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 } }, /** * @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. * @locale */ buttonText: 'Browse...', /** * @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, tabIndex: me.tabIndex, text: me.buttonText, style: me.buttonOnly ? '' : me.getButtonMarginProp() + me.buttonMargin + 'px', accept: me.accept, inputName: me.getName(), listeners: { scope: me, change: me.onFileChange } }, me.buttonConfig); return me.callParent([triggers]); } // <debug> else { Ext.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. inputEl.on('focus', me.onInputFocus, me); inputEl.on('mousedown', me.onInputMouseDown, 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.isIE8) { 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; }, /** * @method setEmptyText * @hide */ setEmptyText: Ext.emptyFn, /** * @method setValue * @hide */ 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; } }, doDestroy: function() { this.fileInputEl = this.button = null; this.callParent(); }, getButtonMarginProp: function() { return this.getInherited().rtl ? 'margin-right:' : 'margin-left:'; }, onInputFocus: function(e) { var me = this; // Text field onFocus() won't call select() so we need to duplicate it here if (me.selectOnFocus && document.activeElement === me.inputEl.dom) { me.inputEl.dom.select(); } me.focus(); // Switching focus from read only input element to file input // results in incorrect positioning of the file input. // Adding and removing position: relative helps to fix that. // See https://sencha.jira.com/browse/EXTJS-18933 if (Ext.isIE9m) { me.fileInputEl.addCls(Ext.baseCSSPrefix + 'position-relative'); me.fileInputEl.removeCls(Ext.baseCSSPrefix + 'position-relative'); } }, onInputMouseDown: function(e) { // Some browsers will show the cursor even if the input is read only, // which will be visible in the short instant between inputEl focusing // and subsequent focus jump to the FileButton. Preventing inputEl from // focusing eliminates that flicker. e.preventDefault(); this.focus(); }, privates: { getFocusEl: function() { return this.button; }, getFocusClsEl: Ext.privateFn }});