PDA

View Full Version : [FIXED-79][3.0.0/2.x] Still have FileUploadField Reset bug



azbok
10 Jul 2009, 9:34 AM
I didn't find any mentions of this in the 3.0 bugs, so I figured I'd mention it.

If you visit: http://extjs.com/deploy/dev/examples/form/file-upload.html

Problem:
The file field does not display the file after choosing, reset and choosing again.

Steps To Reproduce:
1) Click on "File Upload Form" photo icon to load a file
2) Click Reset
3) Click on "File Upload Form" photo icon to load the same file as in #1

The problem is described in this thread along with various solutions:
http://extjs.com/forum/showthread.php?p=221504

Thanks

mjlecomte
10 Jul 2009, 10:24 AM
Thanks for the report. Marking as OPEN

azbok
12 Jul 2009, 5:26 PM
Since I was working on the FileUploadField again, I figured I'd put all the stuff together in an override from primarily http://extjs.com/forum/showthread.php?p=236749#post236749

I noticed the onDestroy() function below gets called twice. I dunno if it's a big deal having things destroyed twice.

My own personal preference is to reset the form itself instead of doing the delete and recreate (a delete / recreate in lower level languages causes more memory fragmentation, I dunno the case for javascript).

This works for me for 3.0RC2. Hopefully it should work for 3.0 as well!

The red highlighted code, is code that is different from the original FileUploadField code.



Ext.override(Ext.ux.FileUploadField, {

onRender : function(ct, position) {
Ext.ux.FileUploadField.superclass.onRender.call(this, ct, position);

this.wrap = this.el.wrap({cls:'x-form-field-wrap x-form-file-wrap'});
this.el.addClass('x-form-file-text');
this.el.dom.removeAttribute('name');

this.fileInput = this.wrap.createChild({
id : this.getFileInputId(),
name : this.name||this.getId(),
cls : 'x-form-file',
tag : 'input',
type : 'file',
size : 1
});

if( this.disabled ) {
this.fileInput.dom.disabled = true;
}

var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
text : this.buttonText
});

this.button = new Ext.Button(Ext.apply(btnCfg, {
renderTo : this.wrap,
cls : 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : '')
}));

if( this.buttonOnly ) {
this.el.hide();
this.wrap.setWidth(this.button.getEl().getWidth());
}

this.fileInput.on('change', function() {
var v = this.fileInput.dom.value;
this.setValue(v);
this.fireEvent('fileselected', this, v);
}, this);

//make the fake icon button represent the mouse state on the transparent fileInput
this.fileInput.on({
'mouseover' : function() { this.button.addClass(['x-btn-over','x-btn-focus']) },
'mouseout' : function() { this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click']) },
'mousedown' : function() { this.button.addClass('x-btn-click') },
'mouseup' : function() { this.button.removeClass(['x-btn-over','x-btn-focus','x-btn-click']) },
scope : this
})
},

// must reset the actual form in order to properly function
// This fixes the bug: select file, reset, select same file
reset : function() {
Ext.get(this.getFileInputId()).findParentNode("form").reset();
Ext.form.FileUploadField.superclass.reset.call(this);
},

onDestroy : function() {
if ( this.fileInput ) {
Ext.destroy(this.fileInput);
this.fileInput = null;
}

if ( this.button ) {
this.button.destroy();
this.button = null;
}

Ext.ux.FileUploadField.superclass.onDestroy.call(this);
},

onEnable: function()
{
Ext.ux.FileUploadField.superclass.onEnable.call(this);
this.fileInput.dom.disabled = false;
this.button.enable();
},

onDisable: function()
{
Ext.ux.FileUploadField.superclass.onDisable.call(this);
this.fileInput.dom.disabled = true;
this.button.disable();
}
});

Animal
12 Jul 2009, 11:28 PM
File input fields are security sensitive.

Sometimes in a browser-dependent way. They often cannot be reset by placing a string into the value property, since that would be a security violation.

I have an Ext.ux.FileInput class which resets by just creating and inserting a new <input type="file">. It's the only way I found to reset in a reliable way.

Condor
12 Jul 2009, 11:53 PM
I have an Ext.ux.FileInput class which resets by just creating and inserting a new <input type="file">. It's the only way I found to reset in a reliable way.

Correct. That is why I suggested this override (http://extjs.com/forum/showthread.php?p=262550#post262550) in the linked thread.

Animal
13 Jul 2009, 1:16 AM
Good code.

Actually though, I prefer a TwinTriggerField so that there is one trigger for choosing a file, and another for resetting to blank:



Ext.override(Ext.Element, {
fireEvent: (function() {
var HTMLEvts = /^(scroll|resize|load|unload|abort|error)$/,
mouseEvts = /^(click|dblclick|mousedown|mouseup|mouseover|mouseout|contextmenu|mousenter|mouseleave)$/,
UIEvts = /^(focus|blur|select|change|reset|keypress|keydown|keyup)$/,
onPref = /^on/;

return Ext.isIE ? function(e) {
e = e.toLowerCase();
if (!onPref.test(e)) {
e = 'on' + e;
}
this.dom.fireEvent(e, document.createEventObject());
} : function(e) {
e = e.toLowerCase();
e.replace(onPref, '');
var evt;
if (mouseEvts.test(e)) {
var b = this.getBox(),
x = b.x + b.width / 2,
y = b.y + b.height / 2;
evt = document.createEvent("MouseEvents");
evt.initMouseEvent(e, true, true, window, (e=='dblclick')?2:1, x, y, x, y, false, false, false, false, 0, null);
} else if (UIEvts.test(e)) {
evt = document.createEvent("UIEvents");
evt.initUIEvent(e, true, true, window, 0);
} else if (HTMLEvts.test(e)) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(e, true, true);
}
if (evt) {
this.dom.dispatchEvent(evt);
}
}
})()
});




Ext.ux.FileField = Ext.extend(Ext.form.TwinTriggerField, {
trigger1Class: 'x-form-search-trigger',

trigger2Class: 'x-form-clear-trigger',

afterRender: function() {
Ext.ux.FileField.superclass.afterRender.apply(this, arguments);
this.el.dom.name = '';
this.el.dom.readOnly = true;
this.wrap.addClass("x-form-file-field-wrap");
this.createFileEl();
this.keyNav = new Ext.KeyNav(this.el, {
"down" : function(e){
this.onTrigger1Click(e);
},
"enter" : function(e){
this.onTrigger1Click(e);
},
scope: this
});
},

createFileEl: function() {
if (this.fileEl) {
this.fileEl.remove();
}
this.fileEl = this.wrap.insertFirst({
name: this.name,
tabIndex: -1,
cls: 'x-form-file',
tag: 'input',
type: 'file',
size: 0
});
this.fileEl.on({
mousedown: this.onFileMouseDown,
change: this.onFileSelect,
focus: this.onFileFocus,
scope: this
});
this.fileEl.setWidth(this.el.getWidth() + this.triggers[0].getWidth());
},

onResize : function(w, h){
Ext.form.TriggerField.superclass.onResize.call(this, w, h);
var tw = this.triggers[0].getWidth() + this.triggers[1].getWidth();
if(typeof w == 'number'){
this.el.setWidth(this.adjustWidth('input', w - tw));
}
this.wrap.setWidth(this.el.getWidth()+tw);
if (this.fileEl) {
this.fileEl.setWidth(this.el.getWidth() + this.triggers[0].getWidth());
}
},

onFileMouseDown: function() {
this.ignoreFocus = true;
},

// If we are processing a mousedown, then allow it to continue.
// Otherwise, it's a focus event from hiding the file select popup, so cancel it and focus the text input.
onFileFocus: function(e) {
if (this.ignoreFocus) {
delete this.ignoreFocus;
} else {
e.stopEvent();
this.focus(true, true);
}
},

onFileSelect: function(e) {
this.focus();
this.el.dom.value = this.fileEl.dom.value;
this.selectText();
},

// Will only ever be called by the KeyNav. The file input overlays this and intercepts mouse clicks.
// Meh! Only Webkit activates a file input when programatically clicked!
onTrigger1Click: function(e) {
this.fileEl.fireEvent("click");
},

onTrigger2Click: function() {
this.focus();
this.el.dom.value = ''
this.createFileEl();
}
});
Ext.reg('filefield', Ext.ux.FileField);


Together with



.x-form-file-field-wrap .x-form-file {
position: absolute;
left: 0px;
-moz-opacity: 0;
filter:alpha(opacity: 0);
opacity: 0;
z-index: 2;
cursor: pointer;
}
.x-form-file-field-wrap .x-form-twin-triggers .x-form-clear-trigger {
position: absolute;
z-index: 3;
}

Condor
13 Jul 2009, 1:20 AM
Does createEvent/createEventObject work on all browsers?

If it does then this is definitely the best solution.

Animal
13 Jul 2009, 1:26 AM
No. I can only get keyboard operation of this field on Webkit browsers :(

Maybe the other ones will arrive at standards compliance one day. 8-|

azbok
13 Jul 2009, 4:58 AM
The reason why I went with this code, specifically the highlighted line is because of: http://www.w3schools.com/htmldom/dom_obj_form.asp

That reset() is calling the actual DOM form's reset which should be compatible with all the browsers. There's no Ext or application level code which is setting the file field to a blank value and I agree, due to security restrictions, the file field is generally read-only.

If it's the case that reset() off the DOM form element does not work in all the various browsers then I'd agree that deleting / recreating would certainly be better (I'd think that if a form reset doesn't work in a browser, that browser has bigger problems!).

I'd be interested in your thoughts, thanks



reset : function() {
Ext.get(this.getFileInputId()).findParentNode("form").reset();
Ext.form.FileUploadField.superclass.reset.call(this);
},

Animal
13 Jul 2009, 6:05 AM
That's fine if you want reset of the file field to reset the whole form.

But that cannot be used in all cases.

azbok
13 Jul 2009, 6:43 AM
Thanks for that insight. I understand now why you need to delete / recreate the file input itself, in case you have other items in the form that you don't want to reset!