The other day, I got a question from a former collegue of mine who was having trouble styling the ASP.NET FileUpload control, as it renders into a particulary nasty bit of HTML; i.e. an <input type=”file” /> tag.
// The .NET control <asp:FileUpload ID="FileUpload1" runat="server" /> // renders into <input id="FileUpload1" type="file" name="FileUpload1" />
He told me that he would love for it to be just another textbox along with a stupid button. If you have ever tried applying any type of styles to an input field of type file, you probably recognize pain when you see it. Having played around with control adapters before I decided that I would have a go at it. Source code for a modified version of what I ended up with is found through the links at the bottom of this post.
The FileUpload WebControlAdapter
As I had decided to use an adaptive control rendering approach, the first thing that I did was adding a line of XML setup to my .browser file (4-5).
App_Browsers\AdapterMappings.browser
<browsers> <browser refID="Default"> <controlAdapters> <adapter controlType="System.Web.UI.WebControls.FileUpload" adapterType= "EPiServer.CodeSample.ControlAdapters.FileUpload.FileUploadAdapter" /> </controlAdapters> </browser> </browsers>
This tells the original FileUpload control that there is an alternative way of rendering, and that it would be much better off going with that instead.
My plan for this was really not complicated. I would hide the original input type file field, and have the control adapter adding a textbox along with the stupid button to the markup. Then a simple piece of jQuery would act as a bridge between the stylable elements and the one actually delivering the file data to the server. Easy as pie.
One of the benefits of inheriting from the WebControlAdapter rather than from the ControlAdapter base class is that you get all kinds of goodies, such as for instance the virtual methods RenderBeginTag, RenderEndTag as well as RenderContents. Well, actually that about covered all you get.
FileUploadAdapter.cs
protected override void RenderBeginTag(HtmlTextWriter writer) { writer.AddAttribute(HtmlTextWriterAttribute.Class, "file-upload"); writer.RenderBeginTag(HtmlTextWriterTag.Div); base.RenderBeginTag(writer); }
protected override void RenderEndTag(HtmlTextWriter writer) { base.RenderEndTag(writer); writer.RenderEndTag(); // file-upload div }
Overriding the two methods in the snippets above, I created an enclosing div tag (25, 47) adding file-upload (24) to the class attribute; this to make the jQuery less complicated. If I wanted to, I could omit the base calls for these and the following method, then use the Control object of the WebControlAdapter class to construct the render output from scratch. However, it would obviously be unwise inventing the wheel once more.
protected override void RenderContents(HtmlTextWriter writer) { base.RenderContents(writer); writer.AddAttribute(HtmlTextWriterAttribute.Type, "text"); writer.RenderBeginTag(HtmlTextWriterTag.Input); writer.RenderEndTag(); writer.AddAttribute(HtmlTextWriterAttribute.Type, "button"); writer.AddAttribute(HtmlTextWriterAttribute.Value, "..."); writer.RenderBeginTag(HtmlTextWriterTag.Input); writer.RenderEndTag(); }
The RenderContents method is where the more stylable elements are injected; the textfield (34-36) and the button (38-41). In order to hide the original input type file field for the visitor, I added a line of code (18) to OnPreRender event handler getting some inline styling on the element. It could of course be done using proper CSS, but I wanted to show how to access the control itself from the adapter.
protected override void OnPreRender(EventArgs e) { Control.Attributes["style"] += "display:none;"; base.OnPreRender(e); }
Of course, the Page is also accessible from the adapter as you can see in the OnLoad event handler if you have a look at the code.
jQuery’s role in styling the <input type=”file” /> element
At this point, adding a FileUpload control to a page will result in an enclosing div along with the hidden original input element (10) as well as our new textfield (11) and button (12) being rendered.
// The .NET control <asp:FileUpload ID="FileUpload1" runat="server" /> // now renders into <div class="file-upload"> <input id="FileUpload1" type="file" style="display:none;" name="FileUpload1"> <input type="text"> <input type="button" value="..."> </div>
The only thing left to do before handing things over to someone who actually has any taste while styling, is to put together a short client side script handling our two remaining problems; showing the file select dialog, and showing the selected file in the new textbox.
FileUploadAdapter.js
function bindFileSelectionDialogTriggers() { var containers = $("div.file-upload"); var clickables = containers .find('input[type="text"]') .add(containers.find('input[type="button"]')); clickables.click(function () { $(this).siblings('input[type="file"]').trigger('click'); }); };
The function above takes care of the first issue. Basically, all it does is finding all of the newly added textfields and buttons (7-9), and then attaching a click event to them (10). The event in turn triggers a click event on the hidden original input field (11) which displays the file selector dialog. To solve the other problem I wrote the binding function in the snippet below.
function bindTextBoxUpdaters() { var containers = $("div.file-upload"); containers.find('input[type="file"]').change(function () { $(this).siblings('input[type="text"]').val($(this).val()); }); };
All that the function does is attaching a change event handler to all of the original input type file fields (16), causing the value to be copied over to the visible textbox (17) as soon as the visitor selects a file. However, this will only copy the filename part of the value; not the entire path that is displayed in the original input field. This is by design and due to security concerns; although there are workarounds, I do not feel it being worth an effort since the filename is enough feedback to satisfy the visitor. It is likey that they also already know where on their harddrive they selected the file.
Source code
Source code: StylableFileUploadControl_src_1.1.1.0.zip (Latest)
Droppable binary: StylableFileUploadControl_binary_1.1.1.0.zip (Latest)
GitHub link: https://github.com/matkun/Blog/tree/master/StylableFileUploadControl
It is not work in Google Chrome. In Explorer and FF – OK. In Chrome not open dialog box.
Seems like you are correct; thanks for the feedback. Chrome does not allow triggering clicks on <input type=”file” .. /> elements if they are invisible (like display: none; visibility: hidden;), while FireFox and Internet Explorer do. My guess is that it may very well be security related, just like you cannot set the filename programmatically.
You could probably solve it by using something in the lines of .show() and .hide() on the element as you are triggering the click, but already at this point it is starting to smell like an ugly hack.
In the commetns at StackOverflow they are discussing alternative ways of hiding the element, such as using opacity for instance. Not sure that I would go there though, it kind of defeats the reason for creating this adapter in the first place.