Well, you know the good old <input type=”file” /> widget in html, the one used for selecting files to be uploaded on a web page, right.

The problem with this widget is its limited support for styling and scripting compared to other html tags (probably due to security concerns). You cannot style or specify text for the “Browse” button for this widget, for example.

In my case, I actually needed to use an ExtJs button for selecting files to be uploaded. I needed it to look exactly like an ExtJs button, yet pop-up a dialog for selecting file when the button is clicked. Once selected, I needed to show the selected file at another place in the UI. You can compare this to “Attach a file” option in Gmail, you click on the link to select a file, and when you select it, Gmail shows the selected file above the link and the link continues to be there for selecting another file, I needed something similar but using an ExtJs button instead (if you haven’t noticed it, its interesting to note that the “Attach a file” button in Gmail is actually a Flash movie, try right-clicking on it and notice the context menu).

My initial instinct was to somehow trigger the “click” event on a “file” input field when an ExtJs button was clicked to show the file selection dialog (that’s the only way in html/javascript you can ask browser to open file selection dialog, by clicking an <input type=”file” /> field). However, I noticed that FireFox does not support this, and you cannot manually fire a “click” event on “file” field in FF.

Flash was never an option for me (there is a way you can do it in Flash, and that’s why Gmail uses Flash for the purpose), because the same page needed to behave similarly in mobile device browsers also, which as you may know do not support Flash.

Now after some fiddling around, I thought of overriding the ExtJs button rendering and render a <input type=”file” /> tag instead of the default <button /> tag for the ExtJs button. There was some success, but the ExtJs Button was not looking good across browsers. As I was searching for better ways to style the “file” input field, I stumbled upon this excellent article on quirksmode.org:
CSS2/DOM – Styleing an input type=file

I very much liked the idea there, which basically involves overlapping a transparent “file” input field over any other styled html content. Because the “file” field is transparent (opacity: 0), you see the underlying styled html. But because the “file” field is still present over the html, clicking html content actually triggers the click on the “file” field leading the browser to show the file selection dialog.

I immediately felt this was the trick I needed in my situation too. I would overlap a “file” field over an ExtJs button and the rest should be as simple as it gets. So, the only remaining concern now was to overlap the “file” field over an ExtJs button consistently across browsers.

You can see the result for the same below:

 

You can see examples for a normal ExtJs button, an ExtJs SplitButton as well as a plain html button above, all doing the same thing that I needed. Try clicking all the buttons and select a file on the dialog.

Here’s the code for this functionality for the ExtJs buttons:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }Ext.override(Ext.Button, {
actAsFileUpload: false,

selectedFileOnce: function(e, t, o) {
var el = this;

var inputEl = Ext.fly(t);
inputEl.removeClass(‘z-button-file-input’);
inputEl.setStyle({ width: ”, height: ” });
inputEl.purgeAllListeners();

t.parentNode.removeChild(t);

el.fireEvent(‘fileselected’, el, t);

this.addNewInputEl();
},

syncInputElSize: function() {
var el = this;

var buttonEl = el.el.child(‘button’);
var inputEl = el.wrap.child(‘input’);

var width = buttonEl.getWidth();
var height = buttonEl.getHeight();
inputEl.setWidth(width + 5);
inputEl.setHeight(height + 8);
},

addNewInputEl: function() {
var el = this;

var buttonEl = el.el.child(‘button’);

var inputEl = document.createElement(‘input’);
inputEl.type = ‘file’;
inputEl = Ext.fly(inputEl);
inputEl.addClass(‘z-button-file-input’);

inputEl.on(‘change’, el.selectedFileOnce, el, { single: true });
inputEl.on({
scope: el,
mouseenter: function() {
this.addClass([‘x-btn-over’, ‘x-btn-focus’])
},
mouseleave: function() {
this.removeClass([‘x-btn-over’, ‘x-btn-focus’, ‘x-btn-click’])
},
mousedown: function() {
this.addClass(‘x-btn-click’)
},
mouseup: function() {
this.removeClass([‘x-btn-over’, ‘x-btn-focus’, ‘x-btn-click’])
}
});

inputEl.insertBefore(el.el);

this.syncInputElSize();
},

initComponent: Ext.Button.prototype.initComponent.createInterceptor(function() {
this.addEvents(‘fileselected’);
}),

onRender: Ext.Button.prototype.onRender.createSequence(function(ct, position) {
if (this.actAsFileUpload) {
var el = this;
el.wrap = el.el.wrap({ style: { position: ‘relative’} });
el.el.setStyle({ position: ‘absolute’, top: ‘0px’, left: ‘0px’, zIndex: 1 });

el.addNewInputEl();
}
})
});{/syntaxhighlighter}

In the above code, I have overriden the base Ext.Button class and added a config option “actAsFileUpload” together with some methods to it. When you actually instantize a Button/SplitButton and pass “true” for “actAsFileUpload”, the onRender sequenced method wraps the default Button html in a <div> with its “position” set to “relative”. Further the button itself is positioned absolutely inside this div. 

Next, an <input type”file” /> is instantized and placed transparently over the button. When you think you are clicking the button, its actually the “file” field which receives the click and the browser shows the dialog. When you select a file there, the onchange listener for the file field invokes the “fileselected” event on the button which you can handle to do whatever you want with the file input field. Here’s what the above code does in “fileselected” event listener:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }new Ext.Button({
text: “Click me”,
renderTo: ‘div1’,
actAsFileUpload: true,
listeners: {
fileselected: function(btn, t) {
document.getElementById(‘fileDiv’).appendChild(t);
}
}
});{/syntaxhighlighter}

Cool, isn’t it.. Now for the pure javascript button, here’s the code that places a field field over it and handles file selection subsequently:

 

{syntaxhighlighter brush: jscript;fontsize: 100; first-line: 1; }function fileInputChanged() {
var t = this;

t.setAttribute(‘class’, ”);
t.style.position = ”;
t.style.width = ”;
t.style.height = ”;

t.onchange = ”;

t.parentNode.removeChild(t);
document.getElementById(‘fileDiv’).appendChild(t);

addNewInputEl(document.getElementById(‘btn1’));
}

function addNewInputEl(button) {
var inputEl = document.createElement(‘input’);
inputEl.type = ‘file’;

inputEl.setAttribute(‘class’, ‘z-button-file-input’);
inputEl.style.position = ‘relative’;
inputEl.style.width = button.offsetWidth;
inputEl.style.height = button.offsetHeight;

inputEl.onchange = fileInputChanged;

button.parentNode.insertBefore(inputEl, button);
}

var btn = document.getElementById(‘btn1’);
addNewInputEl(btn);{/syntaxhighlighter}

The logic is similat as for an ExtJs button, just that the absence of the utility DOM manipulation methods makes it less fun 🙂

You will find the complete code for the above example attached below.