PDA

View Full Version : File Upload Nightmare III (featuring Freddy Krueger and Bela Lugosi)



cesarulo
24 May 2007, 3:16 PM
Hey people,

well, everybody on this forum must hate me by now, but I'm unfortunately stuck with one of these real-world problems that need a solution. My apologies about this topic-fixation.

This is the third in a daisy-chain of fascinating threads, installment number 2 being found here (http://extjs.com/forum/showthread.php?t=6689).

In it, Animal verifies that a form created from no markup with Ext.form.Form will NOT be created with the correct enctype for file uploads even if fileUpload:true is given to its constructor. He then moves on to give me this undocumented fix:



var myForm = new Ext.form.Form({
autoCreate: {
tag: 'form',
id : "upload-form",
method : 'POST',
enctype : "multipart/form-data"
}
});


I can almost taste my victory. I give it a shot:



function initFileUploadForm(scope){

var uploadForm = new Ext.form.Form({
//target script of the form action, baseParams will be passed in POST variables
labelAlign: 'right',
fileUpload: true,
baseParams: {path: scope.PWD},
autoCreate: {
tag: 'form',
id : "file_upload_form",
method : 'POST',
enctype : "multipart/form-data"
}
});


//Description textarea
var descriptionField = new Ext.form.TextField({
allowBlank: false,
id: 'file_upload_description',
name: 'description',
fieldLabel: 'Description',
width: 300,
blankText: 'Please type a description'
});

//File upload input
var fileField = new Ext.form.TextField({
allowBlank: false,
id: 'upload_file',
inputType: 'file',
name: 'upload_file',
fieldLabel: 'File',
width: 300,
blankText: 'Please choose a file'
});

//define an UpdateManager so we can control the upload server script's response
scope.fileUploadUpdMgr = new Ext.UpdateManager(Ext.get('file_upload_messages'));

//add fields to form
uploadForm.add(descriptionField);
uploadForm.add(fileField);
uploadForm.addButton({
text: 'Upload',
handler: function(){
scope.fileUploadUpdMgr.formUpdate('file_upload_form', undefined, true, scope.uploadResponse);
}
});
uploadForm.addButton({
text: 'Cancel',
handler: function(){scope.fileUploadDialog.hide()}
});

//render it
scope.fileUploadDialog.body.dom.innerHTML = '<div id="file_upload_dialog_body"></div>';
uploadForm.render('file_upload_dialog_body');

//hook the form up to the GUIContainer that called the function
scope.fileUploadForm = uploadForm;

}//function initFileUploadForm(scope)



function initFileUploadDialog(scope){
var dlg = new Ext.BasicDialog('file_upload_dialog', {
autoCreate: true,
height: 150,
width: 500,
minHeight: 100,
minWidth: 150,
modal: true,
shadow: true,
title: 'File Upload',
syncHeightBeforeShow: true
});
dlg.addKeyListener(27, dlg.hide, dlg); // ESC can also close the dialog
scope.fileUploadDialog = dlg;
}//function initFileUploadDialog(scope)


That's there just in case I'm doing something really stupid and obvious and I'm not seeing it. Anyway, let's cut to the chase: what's my @#^$&@ problem now?!?

In short: that code aborts with "this.el is not defined" in the following place of ext-all-debug.js, in Ext.extend(Ext.form.Layout, Ext.Component, ...:



onRender : function(ct){
if(this.el){
this.el = Ext.get(this.el);
}else {
var cfg = this.getAutoCreate();
this.el = ct.createChild(cfg);
}
if(this.style){
this.el.applyStyles(this.style);
}
if(this.labelAlign){
this.el.addClass('x-form-label-'+this.labelAlign); //<--HERE
}
if(this.hideLabels){
this.labelStyle = "display:none";
this.elementStyle = "padding-left:0;";
}else{
if(typeof this.labelWidth == 'number'){
this.labelStyle = "width:"+this.labelWidth+"px;";
this.elementStyle = "padding-left:"+((this.labelWidth+(typeof this.labelPad == 'number' ? this.labelPad : 5))+'px')+";";
}
if(this.labelAlign == 'top'){
this.labelStyle = "width:auto;";
this.elementStyle = "padding-left:0;";
}
}
var stack = this.stack;
var slen = stack.length;
if(slen > 0){
if(!this.fieldTpl){
var t = new Ext.Template(
'<div class="x-form-item {5}">',
'<label for="{0}" style="{2}">{1}{4}</label>',
'<div class="x-form-element" id="x-form-el-{0}" style="{3}">',
'</div>',
'</div><div class="x-form-clear-left"></div>'
);
t.disableFormats = true;
t.compile();
Ext.form.Layout.prototype.fieldTpl = t;
}
for(var i = 0; i < slen; i++) {
if(stack[i].isFormField){
this.renderField(stack[i]);
}else{
this.renderComponent(stack[i]);
}
}
}
if(this.clear){
this.el.createChild({cls:'x-form-clear'});
}
},


So I spent about 4-5 hours today stepping through the code and attempting to characterize the situation so I could describe it succinctly in this post. And here goes what I think might be the problem:

Let's go to Ext.extend(Ext.form.Form, Ext.form.BasicForm,... and get method render() (i.e., find the place in the source where Ext.Form.render() is defined). Here goes its code for clarity's sake:



render : function(ct){
ct = Ext.get(ct);
var o = this.autoCreate || {
tag: 'form',
method : this.method || 'POST',
id : this.id || Ext.id()
};

this.initEl(ct.createChild(o));

this.root.render(this.el); //<-- HERE

this.items.each(function(f){
f.render('x-form-el-'+f.id);
});

if(this.buttons.length > 0){
var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
cls:"x-form-btns x-form-btns-"+this.buttonAlign,
html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
}}, null, true);
var tr = tb.getElementsByTagName('tr')[0];
for(var i = 0, len = this.buttons.length; i < len; i++) {
var b = this.buttons[i];
var td = document.createElement('td');
td.className = 'x-form-btn-td';
b.render(tr.appendChild(td));
}
}
return this;
},


Notice the line that says "this.root.render(this.el);". If I step OVER it in FireBug, this is where the code breaks. Of course I could go deeper in, but I think this is the line that exposes the conceptual quandary that we're facing.

1) in this context, this is the Form object that I created, my uploadForm.

2) this.root is an Ext.form.Layout object. Supposed to be there to act as the container to render the form.

3) this.el is, I *think* an Ext.Element object wrapping the DOM Element that underlies the form

4) looking at the docs, Ext.form.Layout.render() (whose call is instantiated by this.root.render(this.el); here) expects an Ext.Element/HTMLElement/String-to-id-an-HTMLElement, and its task is to use that passed argument as a container to render the Form (this) INSIDE IT.

But... it's getting an Ext.Element not for a container, but for a FORM! I've actually traced the code all the way down to Ext.extend(Ext.form.Layout, Ext.Component,... method onrender():



onRender : function(ct){
if(this.el){
this.el = Ext.get(this.el);
}else {
var cfg = this.getAutoCreate();
this.el = ct.createChild(cfg); //<--HERE
}
if(this.style){
this.el.applyStyles(this.style);
}
if(this.labelAlign){
this.el.addClass('x-form-label-'+this.labelAlign);
}
if(this.hideLabels){
this.labelStyle = "display:none";
this.elementStyle = "padding-left:0;";
}else{
if(typeof this.labelWidth == 'number'){
.
.
.
.


Where, in the line that says


this.el = ct.createChild(cfg);

this.el is being returned as null because... well, you're asking the DOM to insert a form into another form - ain't gonna happen.

Now this could be a bug in Ext, or it could be that I'm the ***** who's asking Ext to shove a form into another form. That being said, I've looked at my code until I had to squint, and I can't seem to find the place where I'm saying that. It looks to me like I define a form, add some fields and buttons to it, define a BasicDialog, and then try to put the Form in the Dialog, and render the whole shebang. Plus, if I remove the autoCreate bit Animal gave me and replace it with a simple true value, as it was, well, now it renders, but we're back to the point where it didn't have the proper enctype.

Well, my apologies once more, but I really need a solution for this and I'm not sure what else to do. Thanks once more Animal for all your help, and if you're gonna be the one on this, I'm sure we'll figure it out sooner or later.

Best,

cesarulo
24 May 2007, 3:26 PM
Oh, BTW: I'm on a plane tomorrow, coming back Sunday afternoon MX time. Please don't interpret my lack of answer as lack of interest. I'll be on this as soon as I get back. Cheers

Animal
24 May 2007, 11:31 PM
//render it
scope.fileUploadDialog.body.dom.innerHTML = '<div id="file_upload_dialog_body"></div>';
uploadForm.render('file_upload_dialog_body');



it could be that you're rendering into a nonexistent container. "file_upload_dialog_body" must exist for the form to render properly. innerHTML is asynchronous. Try either sending the file_upload_dialog_body down with page markup, or using DomHelper methods with useDom = true.

I think you need to step back, take a deep breath and think your plan through. It's not that bad. All the Form constructor+render does is create a form element, and insert it into the DOM inside an element you specify to the render call.

when you step through render, does "ct" contain a "dom" property that is the div you are expecting?

Animal
24 May 2007, 11:35 PM
scope.fileUploadDialog.body.dom.innerHTML = '<div id="file_upload_dialog_body"></div>';
uploadForm.render('file_upload_dialog_body');

//hook the form up to the GUIContainer that called the function
scope.fileUploadForm = uploadForm;

}//function initFileUploadForm(scope)



function initFileUploadDialog(scope){
var dlg = new Ext.BasicDialog('file_upload_dialog', {


So "scope.fileUploadDialog" is the Dialog created by that call to new Ext.BasicDialog???

Why not just render directly into scope.fileUploadDialog.body.dom instead of poking a DIV into it and rendering the form into that?

Step back and take a look at what you're doing.

Richard
25 May 2007, 7:44 AM
Hi,

Maybe not the best example since it has been coded for my dialog structure, but you should get the meaning.

The form renders to a div 'tabDialogGeneral' which is a container in my Dialog.

This posts the file to an ASP.net backend which sends an XML response for success true/false as per the 'XML form sample'.

Tested on IE7 and FF2.

Hope this helps



Form = new Ext.form.Form
(
{
fileUpload: true,
errorReader: new Ext.form.XmlErrorReader()
}
);


Form.add
(
new Ext.form.TextField({fieldLabel:'Image',name:'txtFile',inputType:'file',width:190})
);

SubmitFunction=function()
{

Form.submit({
url:'your url',
success:function(form,action)
{
// OK
},
failure:function(form,action)
{
// NOT OK
}
});
}

SubmitButton=myDialogGeneral.Dialog.addButton('Update', SubmitFunction, myDialogGeneral.Dialog);

Form.render('tabDialogGeneral');

cesarulo
28 May 2007, 11:31 AM
Hey guys,

first of all thanks for all your answers.

Second of all my apologies about my persistence. This is getting ridiculous.

Now, to the reply: Animal, I was doing that div with the innerHTML because I found it in an example somewhere in the forums. I could find the link for you but it doesn't really matter I don't think. In any case, here's what I think are the relevant facts about that way of coding it versus the one you suggest:

1) if I set the Form constructor's config object autoCreate property to true, it renders, but it's got the wrong enctype and the file doesn't upload.

2) If I use the autoCreate object you gave me, it seems like the form's enctype is probably correct, but I haven't actually verified this because the form won't even render by virtue of the 'form within a form' stunt that Ext seems to be trying to pull here.

3) I tried BOTH doing it with that hardcoded div shoved into the innerHTML property AND the way you say, rendering it directly into scope.fileUploadDialog.body.dom. In both cases the same thing happens in case 1) above, and the same is true for case 2). This is, whether I do with the div in the innerHTML or the scope.fileUploadDialog.body.dom seems to have absolutely no effect on the outcome.

4) Answer to your question:

when you step through render, does "ct" contain a "dom" property that is the div you are expecting
is "yes".

5) I've basically tried four combinations of things here: the 2 possibilities of rendering the form in the innerHTML generated on the fly by me as detailed above vs. using scope.fileUploadDialog.body.dom, in combination with the 2 possibilities of passing fileUpload:true vs. passing fileUpload as the object with 4 properties that Animal suggests in the previous post, in each case with the aforementioned results. ALL OTHER CODE HAS REMAINED UNTOUCHED and has stayed as shown above.


By virtue of all of the above, I'm more and more convinced that we're looking at some kind of strange bug with Ext here. Think about it: there's only two possible results being

1) the form doesn't render at all, or
2) the form renders, but with the wrong enctype and the file doesn't upload.

This, after I addressed the only possible bug that Animal seems to have found in my code.

Now, I know the few people who have read this succession of threads and are rolling their eyes and going "oh, boy! here he comes again!" are likely about as bored with this problem as Animal and myself are, and I have no interest in being a difficult user here. I really, really just need to get this application to work, and I care little about theoretical considerations or "the truth" about why this code doesn't work.

This leaves me with the only possibility of abandoning this approach altogether and trying out a completely different way of solving this problem. For that, Richard, I have to say thanks a lot for that code. It might end up being the scaffolding of a workable solution.

In the meantime, if you, Animal, or anyone else in the team or among the users can help me sort out why that code (which looks perfect to me and no one has provided hard evidence to the contrary) doesn't work, it would be great. But I'm not really expecting it or planning to spend too much more of my own time figuring it out.

Just a final suggestion before I throw all that code away: Animal, don't you think this problem's interesting enough to merit Jack's attention, or someone else's in the development team? I think we might be looking at either a big bug in Ext or a big misconception on my part whose elucidation might be very useful to the whole community. But as I say, I will not contend this point. I just need a workable solution of any kind.

Cheers everybody and thanks so much for your infinite patience, especially Animal.

cesarulo
28 May 2007, 12:23 PM
Hey, sorry; me once more. I have tens of pages of notes I wrote while stepping thru the code and trying to understand what was going on. PLEASE consider this for just a second.

In
Ext.extend(Ext.form.Form, Ext.form.BasicForm, {..., function render() (i.e., the definition of BasicForm.render()) goes:



render : function(ct){
ct = Ext.get(ct);
var o = this.autoCreate || {
tag: 'form',
method : this.method || 'POST',
id : this.id || Ext.id()
};

this.initEl(ct.createChild(o)); //<-- check this out

this.root.render(this.el); //<-- and this

this.items.each(function(f){
f.render('x-form-el-'+f.id);
});

if(this.buttons.length > 0){
var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
cls:"x-form-btns x-form-btns-"+this.buttonAlign,
html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
}}, null, true);
var tr = tb.getElementsByTagName('tr')[0];
for(var i = 0, len = this.buttons.length; i < len; i++) {
var b = this.buttons[i];
var td = document.createElement('td');
td.className = 'x-form-btn-td';
b.render(tr.appendChild(td));
}
}
return this;
},



In the line that goes


this.initEl(ct.createChild(o));

initEl is invoked. Please, please take a look at it. What it does is initialize this.el to Ext.get(ct.createChild(o)). The code right below it calls this.root.render(this.el);, that is, it wants to render this (the form) in this.el (a container of some sort, like a BasicDialog, one would think).

And yet... ct.createChild(o) is returning the FORM! This code is telling the form (this) to render into itself (this.el). I think the developer who wrote this code made the very understandable mistake of thinking that ct.createChild(o) would return ct (the container - i.e. the DIALOG), when in reality it returns o (the containee - i.e. the FORM).

Can we have someone from the development team look at this? I think we have a bug...

Thanks again and my renewed apologies about my relentlessness.

Best,

dj
28 May 2007, 8:05 PM
it's "this.root.render(this.el);"
So the code renders the root-container of the Form into the Form. That's not an error. (otherwise, why should it work in the case when it's not a file upload?)

cesarulo
28 May 2007, 8:40 PM
it's "this.root.render(this.el);"
So the code renders the root-container of the Form into the Form. That's not an error. (otherwise, why should it work in the case when it's not a file upload?)

Hey dj,

I have no idea. All I know is that that code is attempting to render a form within a form. I've seen the code as it attempts to do this, have you read my previous posts? If I know my HTML correctly, the children you can append to a form are things like display elements such as tables or divs, and then any input elements you want in the form. NOT other forms.

Why it tries to insert a form within a form when the enctype is multipart/form-data, and not when it's a regular form, all else being equal, as I clearly indicate above, I have no clue. With finite time and resources, and deadlines on a project for which a simple task is taking me close to a week now, I've only had time to focus on the case that doesn't work, and haven't had much time for the infinity of possible combinations that DO work, but have nothing to do with what I'm trying to do here by virtue of their not constituting solutions to my problem, such as forms that are *not* file uploads.

Perhaps I'm wrong about my analysis of the possible source of the problem. I've only spent an average of 2-3 hours of each day trying to diagnose it, as I have to spend the rest of the day doing things that will actually help me pay the rent and other such silly things, you know. It was just a suggestion. In any case, what I'm really hoping for is a solution that *helps* me (as in the name of this forum, "Help") get this thing to work. It's a pretty simple problem. I want to render a Form in a BasicDialog. And so far no one has been able to tell me what's wrong with my code.

So you see, I'm really sorry, but I don't have an answer to your question. Do you happen to know anyone who has an answer to mine?

dj
28 May 2007, 8:51 PM
... I want to render a Form in a BasicDialog. ...

this BasicDialog doesn't has tabs, does it? If it does the form elements are outside the form, but that's another story...



... And so far no one has been able to tell me what's wrong with my code.

So you see, I'm really sorry, but I don't have an answer to your question. Do you happen to know anyone who has an answer to mine?

Would be good to have a live-demo of your problem where one could step through with firebug and reproduce your error easily.

cesarulo
28 May 2007, 8:52 PM
So the code renders the root-container of the Form into the Form.

Please correct me if I have my terminology wrong, I'm *really* an Ext n00b, I've been working with it seriously for less than a month. I don't know what a 'root container' is in Ext parlance, but if it's a container in the traditional HTML sense... and this code is trying to render the container into the form, as you say... isn't that like trying to put the bottle into your water? Isn't what we're trying to do *precisely* the opposite, rendering the form into the container?

This might be the one misconception that's killing me. If you can knock that down, please so do quick and painlessly. The sooner I understand what's wrong with me, the sooner I can get this thing to work.

Thanks,

cesarulo
28 May 2007, 9:01 PM
Hey dj,

thanks for your prompt reply! I *really* appreciate your interest and apologize for my short temper and sarcasm. Having been in this business for a while you're probably no stranger to it.

Anyway, I have some restrictions on much of the project internals I can disclose, but I'll consult with my boss and I'm sure he'll let me isolate the file upload form problem in some domain in our servers. Have to ask him tho, can't do till tomorrow.

On with answering your questions:


this BasicDialog doesn't has tabs, does it? If it does the form elements are outside the form, but that's another story...

Nope. No tabs. Just a plain BasicDialog (perhaps this is a mistake?). What I want is a little modal window that floats over the GUI and doesn't let you do anything other than upload a file or cancel.


Would be good to have a live-demo of your problem where one could step through with firebug and reproduce your error easily.

Absolutely. I can't stress enough how grateful I am for your interest. As mentioned above, will consult with the Big Man and set up a demo ASAP. Will let the community know by this very means.

Cheers,

dj
28 May 2007, 9:18 PM
my terminology was wrong - i was talking about the root-layout-container (have a look at src/widgets/form/Layout.js)

An Ext.form.Form consists of multiple (possiblly nested) layout-containers. Every time you call form.container(..), form.fieldset(...) etc. you create a layout-container. These containers are not direct children of the the form but children of a "root container". So consider this form:


var form = new Ext.form.Form(...);
form.fieldset({id:"fset1",legend:"Calendar"},
new MMEPR.CalendarSelectorField(...));
form.fieldset({id:"fset2",legend:"Start and end dates"},
new Ext.form.DateField(...),
new Ext.form.DateField(...);


this form has internally the following structure:


form (Ext.form.Form)
+- root (Ext.form.Layout)
+- fset1 (Ext.form.FieldSet)
+- field
+- fset2 (Ext.form.FieldSet)
+- field
+- field


and if you render the form it will automatically render the root-layout-container and this one renders its children (fset1&fset2) and so on.

cesarulo
28 May 2007, 9:23 PM
It's a good thing I mentioned I'm a total noob :D

You've just taught me something very valuable that I had no clue of.
Unfortunately it's past midnight here and I gotta get up at a decent time tomorrow. I'll be looking at this in detail, evaluate the other solution proposed, and keep in touch until the problem is solved or I completely give up on it :)

Thanks a lot dj. I'll see about that demo and get back in touch tomorrow.

Animal
28 May 2007, 11:43 PM
It's not that difficult, and Ext is not that difficult. Al Ext does is wrap up some complex javascript/DOM techniques in a simpler to use manner!

Your Form needs an object autoCreate so that it creates its Element with the correct enctype.

Then render it directly into the body element of a dialog.

That's it. No complexity involved, no need for thinking you have to be some kind of guru, or that Ext is in some way complicated.

danielv
19 Jun 2007, 1:22 AM
I have a feeling that the problem could be here:


//render it
scope.fileUploadDialog.body.dom.innerHTML = '<div id="file_upload_dialog_body"></div>';
uploadForm.render('file_upload_dialog_body');

you are inserting the container you are rendering into dynamically.

i had a similar problem where i requested the instantiation of an object, and call its method on the next line. however, due to whatever magical asynchronous threading powers that be, the second call was executing before the first completed all of its side effects.

make sure that the container div is successfully being created.

however, by reading your ghoulishly titled post, i may have answered my own question on reading the results of a post. :)

cheers

cesarulo
20 Jun 2007, 6:49 PM
Hey danielv,

thanks a lot for providing this! I saw your post on the other thread (number 1 of 3, this one it the third one). I will give a shot to these things as soon as I have a chance, I'm just really busy with another project right now.

Just so you know that I haven't abandoned the thread! I will try these and report as soon as I get a chance.

aek82
23 Jul 2007, 12:11 AM
Found a widget that lets you upload files in a dialog box. Forum post about it here (http://extjs.com/forum/showthread.php?t=4193&highlight=Dialog+Upload)

and you can download it here (http://www.divergingpath.com/index.cfm/2007/4/1/Ext-UploadDialog-Widget-that-extends-from-LayoutDialog#)


Alex