]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * A file upload field which has custom styling and allows control over the button text and other\r | |
3 | * features of {@link Ext.form.field.Text text fields} like {@link Ext.form.field.Text#emptyText empty text}.\r | |
4 | * It uses a hidden file input element behind the scenes to allow user selection of a file and to\r | |
5 | * perform the actual upload during {@link Ext.form.Basic#submit form submit}.\r | |
6 | *\r | |
7 | * Because there is no secure cross-browser way to programmatically set the value of a file input,\r | |
8 | * the standard Field `setValue` method is not implemented. The `{@link #getValue}` method will return\r | |
9 | * a value that is browser-dependent; some have just the file name, some have a full path, some use\r | |
10 | * a fake path.\r | |
11 | *\r | |
12 | * **IMPORTANT:** File uploads are not performed using normal 'Ajax' techniques; see the description for\r | |
13 | * {@link Ext.form.Basic#hasUpload} for details.\r | |
14 | *\r | |
15 | * # Example Usage\r | |
16 | *\r | |
17 | * @example\r | |
18 | * Ext.create('Ext.form.Panel', {\r | |
19 | * title: 'Upload a Photo',\r | |
20 | * width: 400,\r | |
21 | * bodyPadding: 10,\r | |
22 | * frame: true,\r | |
23 | * renderTo: Ext.getBody(),\r | |
24 | * items: [{\r | |
25 | * xtype: 'filefield',\r | |
26 | * name: 'photo',\r | |
27 | * fieldLabel: 'Photo',\r | |
28 | * labelWidth: 50,\r | |
29 | * msgTarget: 'side',\r | |
30 | * allowBlank: false,\r | |
31 | * anchor: '100%',\r | |
32 | * buttonText: 'Select Photo...'\r | |
33 | * }],\r | |
34 | *\r | |
35 | * buttons: [{\r | |
36 | * text: 'Upload',\r | |
37 | * handler: function() {\r | |
38 | * var form = this.up('form').getForm();\r | |
39 | * if(form.isValid()){\r | |
40 | * form.submit({\r | |
41 | * url: 'photo-upload.php',\r | |
42 | * waitMsg: 'Uploading your photo...',\r | |
43 | * success: function(fp, o) {\r | |
44 | * Ext.Msg.alert('Success', 'Your photo "' + o.result.file + '" has been uploaded.');\r | |
45 | * }\r | |
46 | * });\r | |
47 | * }\r | |
48 | * }\r | |
49 | * }]\r | |
50 | * });\r | |
51 | */\r | |
52 | Ext.define('Ext.form.field.File', {\r | |
53 | extend: 'Ext.form.field.Text',\r | |
54 | alias: ['widget.filefield', 'widget.fileuploadfield'],\r | |
55 | alternateClassName: ['Ext.form.FileUploadField', 'Ext.ux.form.FileUploadField', 'Ext.form.File'],\r | |
56 | requires: [\r | |
57 | 'Ext.form.field.FileButton',\r | |
58 | 'Ext.form.trigger.Component'\r | |
59 | ],\r | |
60 | \r | |
61 | /**\r | |
62 | * @cfg {String} emptyText\r | |
63 | * Overridden to undefined as {@link #emptyText} is not supported with {@link #inputType inputType}:'file' and should be avoided.\r | |
64 | * The default text to place into an empty field.\r | |
65 | */ \r | |
66 | emptyText: undefined,\r | |
67 | \r | |
68 | needArrowKeys: false,\r | |
69 | \r | |
70 | triggers: {\r | |
71 | filebutton: {\r | |
72 | type: 'component',\r | |
73 | hideOnReadOnly: false,\r | |
74 | // Most form fields prevent the default browser action on mousedown of the trigger.\r | |
75 | // This is intended to prevent the field's input element from losing focus when\r | |
76 | // the trigger is clicked. File fields disable this behavior because:\r | |
77 | // 1. The input element does not receive focus when the field is focused. The button does.\r | |
78 | // 2. Preventing the default action of touchstart (translated from mousedown\r | |
79 | // on mobile browsers) prevents the browser's file dialog from opening.\r | |
80 | preventMouseDown: false\r | |
81 | }\r | |
82 | },\r | |
83 | \r | |
84 | //<locale>\r | |
85 | /**\r | |
86 | * @cfg {String} buttonText\r | |
87 | * The button text to display on the upload button. Note that if you supply a value for\r | |
88 | * {@link #buttonConfig}, the buttonConfig.text value will be used instead if available.\r | |
89 | */\r | |
90 | buttonText: 'Browse...',\r | |
91 | //</locale>\r | |
92 | \r | |
93 | /**\r | |
94 | * @cfg {Boolean} buttonOnly\r | |
95 | * True to display the file upload field as a button with no visible text field. If true, all\r | |
96 | * inherited Text members will still be available.\r | |
97 | */\r | |
98 | buttonOnly: false,\r | |
99 | \r | |
100 | /**\r | |
101 | * @cfg {Number} buttonMargin\r | |
102 | * The number of pixels of space reserved between the button and the text field. Note that this only\r | |
103 | * applies if {@link #buttonOnly} = false.\r | |
104 | */\r | |
105 | buttonMargin: 3,\r | |
106 | \r | |
107 | /**\r | |
108 | * @cfg {Boolean} clearOnSubmit\r | |
109 | * True to clear the selected file value when the form this field belongs to\r | |
110 | * is submitted to the server.\r | |
111 | */\r | |
112 | clearOnSubmit: true,\r | |
113 | \r | |
114 | /**\r | |
115 | * @cfg {Object} buttonConfig\r | |
116 | * Specify optional custom button {@link Ext.button.Button} config (eg. iconCls, text) for the upload button\r | |
117 | */\r | |
118 | \r | |
119 | /**\r | |
120 | * @event change\r | |
121 | * Fires when the underlying file input field's value has changed from the user selecting a new file from the system\r | |
122 | * file selection dialog.\r | |
123 | * @param {Ext.ux.form.FileUploadField} this\r | |
124 | * @param {String} value The file value returned by the underlying file input field\r | |
125 | */\r | |
126 | \r | |
127 | /**\r | |
128 | * @property {Ext.dom.Element} fileInputEl\r | |
129 | * A reference to the invisible file input element created for this upload field. Only populated after this\r | |
130 | * component is rendered.\r | |
131 | */\r | |
132 | \r | |
133 | /**\r | |
134 | * @property {Ext.button.Button} button\r | |
135 | * A reference to the trigger Button component created for this upload field. Only populated after this component is\r | |
136 | * rendered.\r | |
137 | */\r | |
138 | \r | |
139 | \r | |
140 | /**\r | |
141 | * @private\r | |
142 | */\r | |
143 | extraFieldBodyCls: Ext.baseCSSPrefix + 'form-file-wrap',\r | |
144 | \r | |
145 | /**\r | |
146 | * @private\r | |
147 | */\r | |
148 | inputCls: Ext.baseCSSPrefix + 'form-text-file',\r | |
149 | \r | |
150 | /**\r | |
151 | * @cfg {Boolean} [readOnly=true]\r | |
152 | * Unlike with other form fields, the readOnly config defaults to true in File field.\r | |
153 | */\r | |
154 | readOnly: true,\r | |
155 | \r | |
156 | /**\r | |
157 | * @cfg {Boolean} editable\r | |
158 | * @inheritdoc\r | |
159 | */\r | |
160 | editable: false,\r | |
161 | \r | |
162 | submitValue: false,\r | |
163 | \r | |
164 | /**\r | |
165 | * Do not show hand pointer over text field since file choose dialog is only shown when clicking in the button\r | |
166 | * @private\r | |
167 | */\r | |
168 | triggerNoEditCls: '',\r | |
169 | \r | |
170 | /**\r | |
171 | * @private\r | |
172 | * Extract the file element, button outer element, and button active element.\r | |
173 | */\r | |
174 | childEls: ['browseButtonWrap'],\r | |
175 | \r | |
176 | /**\r | |
177 | * @private\r | |
178 | */\r | |
179 | applyTriggers: function(triggers) {\r | |
180 | var me = this,\r | |
181 | triggerCfg = (triggers || {}).filebutton;\r | |
182 | \r | |
183 | if (triggerCfg) {\r | |
184 | triggerCfg.component = Ext.apply({\r | |
185 | xtype: 'filebutton',\r | |
186 | ownerCt: me,\r | |
187 | id: me.id + '-button',\r | |
188 | ui: me.ui,\r | |
189 | disabled: me.disabled,\r | |
190 | text: me.buttonText,\r | |
191 | style: me.buttonOnly ? '' : me.getButtonMarginProp() + me.buttonMargin + 'px',\r | |
192 | inputName: me.getName(),\r | |
193 | listeners: {\r | |
194 | scope: me,\r | |
195 | change: me.onFileChange\r | |
196 | }\r | |
197 | }, me.buttonConfig);\r | |
198 | \r | |
199 | return me.callParent([triggers]);\r | |
200 | }\r | |
201 | // <debug>\r | |
202 | else {\r | |
203 | Ext.raise(me.$className + ' requires a valid trigger config containing "filebutton" specification');\r | |
204 | }\r | |
205 | // </debug>\r | |
206 | },\r | |
207 | \r | |
208 | getSubTplData: function(fieldData) {\r | |
209 | var data = this.callParent([fieldData]);\r | |
210 | \r | |
211 | // Input field itself should not be focusable since it's always decorative;\r | |
212 | // however the input element is naturally focusable (and tabbable) so we have to\r | |
213 | // deactivate it by setting its tabIndex to -1.\r | |
214 | data.tabIdx = -1;\r | |
215 | \r | |
216 | return data;\r | |
217 | },\r | |
218 | \r | |
219 | /**\r | |
220 | * @private\r | |
221 | */\r | |
222 | onRender: function() {\r | |
223 | var me = this,\r | |
224 | inputEl, button, buttonEl, trigger;\r | |
225 | \r | |
226 | me.callParent(arguments);\r | |
227 | \r | |
228 | inputEl = me.inputEl;\r | |
229 | //name goes on the fileInput, not the text input\r | |
230 | inputEl.dom.name = ''; \r | |
231 | \r | |
232 | // Some browsers will show a blinking cursor in the field, even if it's readonly. If we do happen\r | |
233 | // to receive focus, forward it on to our focusEl. Also note that in IE, the file input is treated as\r | |
234 | // 2 elements for tabbing purposes (the text, then the button). So as you tab through, it will take 2\r | |
235 | // tabs to get to the next field. As far as I know there's no way around this in any kind of reasonable way.\r | |
236 | inputEl.on('focus', me.onInputFocus, me);\r | |
237 | inputEl.on('mousedown', me.onInputMouseDown, me);\r | |
238 | \r | |
239 | trigger = me.getTrigger('filebutton');\r | |
240 | button = me.button = trigger.component;\r | |
241 | me.fileInputEl = button.fileInputEl;\r | |
242 | buttonEl = button.el;\r | |
243 | \r | |
244 | if (me.buttonOnly) {\r | |
245 | me.inputWrap.setDisplayed(false);\r | |
246 | me.shrinkWrap = 3;\r | |
247 | }\r | |
248 | \r | |
249 | // Ensure the trigger element is sized correctly upon render\r | |
250 | trigger.el.setWidth(buttonEl.getWidth() + buttonEl.getMargin('lr'));\r | |
251 | if (Ext.isIE) {\r | |
252 | me.button.getEl().repaint();\r | |
253 | }\r | |
254 | },\r | |
255 | \r | |
256 | /**\r | |
257 | * Gets the markup to be inserted into the subTplMarkup.\r | |
258 | */\r | |
259 | getTriggerMarkup: function() {\r | |
260 | return '<td id="' + this.id + '-browseButtonWrap" data-ref="browseButtonWrap" role="presentation"></td>';\r | |
261 | },\r | |
262 | \r | |
263 | /**\r | |
264 | * @private\r | |
265 | * Event handler fired when the user selects a file.\r | |
266 | */\r | |
267 | onFileChange: function(button, e, value) {\r | |
268 | this.duringFileSelect = true;\r | |
269 | Ext.form.field.File.superclass.setValue.call(this, value);\r | |
270 | delete this.duringFileSelect;\r | |
271 | },\r | |
272 | \r | |
273 | didValueChange: function(){\r | |
274 | // In the case of the file field, the change event will only ever fire \r | |
275 | // if the value actually changes, so we always want to fire the change event\r | |
276 | // This affects Chrome specifically, because hitting the cancel button will\r | |
277 | // reset the file upload.\r | |
278 | return !!this.duringFileSelect;\r | |
279 | },\r | |
280 | \r | |
281 | /**\r | |
282 | * @method setEmptyText\r | |
283 | * @hide\r | |
284 | */\r | |
285 | setEmptyText: Ext.emptyFn,\r | |
286 | \r | |
287 | /**\r | |
288 | * @method setValue\r | |
289 | * @hide\r | |
290 | */\r | |
291 | setValue: Ext.emptyFn,\r | |
292 | \r | |
293 | reset : function(){\r | |
294 | var me = this,\r | |
295 | clear = me.clearOnSubmit;\r | |
296 | if (me.rendered) {\r | |
297 | me.button.reset(clear);\r | |
298 | me.fileInputEl = me.button.fileInputEl;\r | |
299 | if (clear) {\r | |
300 | me.inputEl.dom.value = '';\r | |
301 | // Reset the underlying value if we're clearing it\r | |
302 | Ext.form.field.File.superclass.setValue.call(this, null);\r | |
303 | }\r | |
304 | }\r | |
305 | me.callParent();\r | |
306 | },\r | |
307 | \r | |
308 | onShow: function(){\r | |
309 | this.callParent();\r | |
310 | // If we started out hidden, the button may have a messed up layout\r | |
311 | // since we don't act like a container\r | |
312 | this.button.updateLayout(); \r | |
313 | },\r | |
314 | \r | |
315 | onDisable: function(){\r | |
316 | this.callParent();\r | |
317 | this.button.disable();\r | |
318 | },\r | |
319 | \r | |
320 | onEnable: function(){\r | |
321 | this.callParent();\r | |
322 | this.button.enable();\r | |
323 | },\r | |
324 | \r | |
325 | /**\r | |
326 | * @method\r | |
327 | * @inheritdoc\r | |
328 | */\r | |
329 | isFileUpload: Ext.returnTrue,\r | |
330 | \r | |
331 | extractFileInput: function() {\r | |
332 | var me = this,\r | |
333 | fileInput;\r | |
334 | \r | |
335 | if (me.rendered) {\r | |
336 | fileInput = me.button.fileInputEl.dom;\r | |
337 | me.reset();\r | |
338 | } else {\r | |
339 | // Create a fake empty field here so it will still be submitted.\r | |
340 | // All other unrendered fields provide a value.\r | |
341 | fileInput = document.createElement('input');\r | |
342 | fileInput.type = 'file';\r | |
343 | fileInput.className = Ext.baseCSSPrefix + 'hidden-display';\r | |
344 | fileInput.name = me.getName();\r | |
345 | }\r | |
346 | return fileInput;\r | |
347 | },\r | |
348 | \r | |
349 | restoreInput: function(el) {\r | |
350 | // If we're not rendered we don't need to do anything, it will be created\r | |
351 | // when we get flushed to the DOM.\r | |
352 | if (this.rendered) {\r | |
353 | var button = this.button;\r | |
354 | button.restoreInput(el);\r | |
355 | this.fileInputEl = button.fileInputEl;\r | |
356 | }\r | |
357 | },\r | |
358 | \r | |
359 | onDestroy: function(){\r | |
360 | this.fileInputEl = this.button = null;\r | |
361 | this.callParent();\r | |
362 | },\r | |
363 | \r | |
364 | getButtonMarginProp: function() {\r | |
365 | return 'margin-left:';\r | |
366 | },\r | |
367 | \r | |
368 | onInputFocus: function(e) {\r | |
369 | this.focus();\r | |
370 | \r | |
371 | // Switching focus from read only input element to file input\r | |
372 | // results in incorrect positioning of the file input.\r | |
373 | // Adding and removing position: relative helps to fix that.\r | |
374 | // See https://sencha.jira.com/browse/EXTJS-18933\r | |
375 | if (Ext.isIE9m) {\r | |
376 | this.fileInputEl.addCls(Ext.baseCSSPrefix + 'position-relative');\r | |
377 | this.fileInputEl.removeCls(Ext.baseCSSPrefix + 'position-relative');\r | |
378 | }\r | |
379 | },\r | |
380 | \r | |
381 | onInputMouseDown: function(e) {\r | |
382 | // Some browsers will show the cursor even if the input is read only,\r | |
383 | // which will be visible in the short instant between inputEl focusing\r | |
384 | // and subsequent focus jump to the FileButton. Preventing inputEl from\r | |
385 | // focusing eliminates that flicker.\r | |
386 | e.preventDefault();\r | |
387 | \r | |
388 | this.focus();\r | |
389 | },\r | |
390 | \r | |
391 | privates: {\r | |
392 | getFocusEl: function() {\r | |
393 | return this.button;\r | |
394 | },\r | |
395 | \r | |
396 | getFocusClsEl: Ext.privateFn\r | |
397 | }\r | |
398 | });\r |