asp.net easy no refresh file upload system

  • 2020-05-17 05:12:47
  • OfStack

ps: flash achieves much better results, but that's not my area of study, and there's no comparison.

Compatibility: ie6/7/8, firefox 3.5.5, opera 10.01, safari 4.0.3, chrome 3.0
Results the preview File upload Select the file rename operation state reset Select the file reset Select the file reset Select the file

ps: to test the system, download the instance test due to background requirements.
ps2: in the full instance file, there is also a file property to view the instance.

Programming instructions

【 upload 】

The most important method in the application is upload, which can be called for a refresh free upload.
The procedure for upload is as follows: first, stop the previous upload with the stop method and decide whether to select the file or not.
Then call _setIframe, _setForm, and _setInput, respectively, to generate the required iframe, form, and input.

If the timeout property is set, the timer is automatically set:
 
if ( this.timeout > 0 ) { 
this._timer = setTimeout( $$F.bind(this._timeout, this), this.timeout * 1000 ); 
} 

ps: after testing, if the delay time is less than 0, ie will cancel the execution, while other browsers will execute as 0.

The application has a _sending property to determine the upload status.
It is set to false when stop (stop), dispose (destroy), _finis (finish), _timeout (timeout).
Set it to true before uploading.

Finally, submit the form and upload it.

【 iframe 】

The program USES the _setIframe function to create iframe without refreshing.

Due to the problem that name of iframe cannot be modified in ie, iframe should be created like this:
 
var iframename = "QUICKUPLOAD_" + QuickUpload._counter++, 
iframe = document.createElement( $$B.ie ? "<iframe name=\"" + iframename + "\">" : "iframe"); 
iframe.name = iframename; 
iframe.style.display = "none"; 

ps: questions about name of iframe refer to the iframe section here.
The ie8 is ready to modify the name, but it cannot be modified in non-standard mode.
It USES the _counter property of the QuickUpload function itself as a calculator, which ensures that the iframe name of each instance will not be repeated.

In order to execute the callback function after the file upload is completed, the _finish function is executed in onload of iframe:
 
var finish = this._fFINISH = $$F.bind(this._finish, this); 
if ( $$B.ie ) { 
iframe.attachEvent( "onload", finish ); 
} else { 
iframe.onload = $$B.opera ? function(){ this.onload = finish; } : finish; 
} 

ie requires attachEvent to bind onload, because setting onload directly in ie is invalid.
Instead of using attachEvent, you can use onreadystatechange instead.
I don't know why, but refer to "the perfect way to tell if iframe is loaded."

There is still a problem with the loading of iframe. Test the following code:

 
<body><div id="msg"> Status: </div></body> 
<script> 
var msg = document.getElementById("msg"); 
var iframe = document.createElement("iframe"); 
iframe.onload = function(){ msg.innerHTML += "onload,"; } 
document.body.appendChild(iframe); 
iframe.src = "http://cloudgamer.cnblogs.com/" 
</script> 

As a result, safari and chrome will trigger onload twice, while opera, ff and ie (please be self-compatible) will trigger onload once.

It is estimated that safari and chrome were loaded for the first time after appendChild, and were loaded before src was set, so it was triggered twice.
If you set iframe to a random src (except null) before inserting body, and indirectly lengthen the first load, it will only be triggered once.
ps: src without setting or null value is equivalent to linking to "about:blank" (blank page).

Then opera, ff and ie may be the first load is too slow, the second overrides the first, so only one onload is triggered.
ps: it could be something else, like browser optimization or something like that, I'm not sure.

For the problem of too fast loading, it can be solved by determining whether the state is uploaded before onload according to _sending.
Although not tested, is there a situation where the _sending setting triggers onload for the first time just before submit?
For this reason, _sending is set after submit in the upload method.
What if onload is triggered before the _sending setting after submit? (... �)
This rarely happens, but if it does, put the _sending setting before submit.

opera also has a troublesome problem. Test the following code:
 
<body> 
<div id="msg"> Status: </div> 
<form action="http://cloudgamer.cnblogs.com/" target="ifr"> 
</form> 
</body> 
<script> 
var msg = document.getElementById("msg"); 
var iframe = document.createElement("iframe"); 
iframe.name = "ifr"; 
iframe.onload = function(){ msg.innerHTML += "onload,"; } 
document.body.appendChild(iframe); 
msg.innerHTML += "submit,"; 
document.forms[0].submit(); 
</script> 

ie and ff show submit,onload, safari and chrome show onload,submit,onload, as shown in analysis 1 above.
opera shows that submit,onload,onload, and onload are both triggered after submit.
This situation cannot be solved by _sending alone.
Can't submit make iframe unload?
Set one src before appendChild, and it will only trigger onload1, which seems to be ok.

Although I don't know the reason, there is a way. One is appendChild, and one is src before appendChild. You can also reset onload in the first onload, just like the program.
But there is uncertainty in both of these approaches, which cannot completely solve the problem, but there is no better way to do it.

ff's onload also has a problem. In the case of server errors such as ERROR_INTERNET_CONNECTION_RESET (the file size exceeds the server limit), onload will not be triggered even if the load is completed, so no solution can be found for the time being.

One of the defects of iframe is that it can only use onload to judge whether the loading is completed, but there is no way to judge whether the loading is successful or not.
There's nothing like XMLHTTP, status, 404 or something like that.
Handle this when you use it, such as allowing file sizes to be uploaded, timeout times, how to handle long periods of no response, and so on.


【 form 】

The program USES the _setForm function to create form for submitting data.

To achieve no refresh upload, form needs special treatment:
 
$$.extend(form, { 
target: this._iframe.name, method: "post", encoding: "multipart/form-data" 
}); 

ps: see the no refresh upload section here for details.

Since form is manually inserted, set the form style 1 to make it "invisible" so as not to affect the original page layout:
 
$$D.setStyle(form, { 
padding: 0, margin: 0, border: 0, 
backgroundColor: "transparent", display: "inline" 
}); 

Also note that the same form control can only correspond to one form.
If the file control itself already has an form, it must be removed before submission:

file.form && $$E.addEvent(file.form, "submit", $$F.bind(this.dispose, this));
The dispose method is used to destroy the program, including removing form.
ps: manually execute the dispose method once if submit is overwritten before submission.

Finally, insert form into dom:

file.parentNode.insertBefore(form, file).appendChild(file);
Insert form in front of the file control, and then insert file into form to ensure that file is in its original position.


【 input 】

If there are other parameters to pass, the program USES the _setInput function to create a form control that passes the data.

Since the generated form has only the file control in it, other parameters can only be generated programmatically if passed.
The program USES a collection of _inputs to hold the form controls currently generated in form.

First, create a form control based on the custom parameter property:

 
for ( name in this.parameter ) { 
var input = form[name]; 
if ( !input ) { 
input = document.createElement("input"); 
input.name = name; input.type = "hidden"; 
form.appendChild(input); 
} 
input.value = this.parameter[name]; 
newInputs[name] = input; 
delete oldInputs[name]; 
} 

When there is no corresponding control for name in form, an hidden control is automatically generated and inserted into form.
Where newInputs is used to record the currently generated control, oldInputs is the _inputs collection.
When the control corresponding to name has been set, the association of the corresponding control is removed from oldInputs.

Then remove the control associated with oldInputs:

for ( name in oldInputs ) { form.removeChild( oldInputs[name] ); }
This removes the unwanted control generated the previous time.

Finally, re-record the current control to _inputs for next use.


【 stop 】

If you want to stop the current upload operation, you can call the stop method.

Generally speaking, when iframe is overloaded, the previous load will be cancelled, so you can cancel the upload by resetting src.
Test the following code:

 
<body> 
<iframe id="ifr" name="ifr"></iframe> 
<form action="http://cloudgamer.cnblogs.com/" target="ifr"> 
</form> 
</body> 
<script> 
document.forms[0].submit(); 
document.getElementById("ifr").src = ""; 
</script> 

All the results can be unloaded, except for opera, for unknown reasons.
There are two ways to do this, one is to submit one action by form, and the other is to remove iframe.
The latter method is more convenient. The _removeIframe method is used to remove iframe directly.
ps: let me know if there's a better way.


【 dispose 】

The dispose method can be called when a program needs to be destroyed at the end of use or for other reasons.

The main thing dispose does is remove iframe and form.
To remove iframe, use the _removeIframe method. First remove onload, then remove iframe from body:

var iframe = this._iframe;
$$B.ie ? iframe.detachEvent( "onload", this._fFINISH ) : ( iframe.onload = null );
document.body.removeChild(iframe); this._iframe = null;
10 points simple, but there is a problem in ff, test the following code:
 
<form target="ifr" action="x"> 
<input id="btn" type="submit" value="click"> 
</form> 
<iframe name="ifr" id="ifr"></iframe> 
<script> 
document.getElementById("btn").onclick = function(){ 
document.getElementById("ifr").onload = function(){ 
this.parentNode.removeChild(this); 
}; 
} 
</script> 

iframe can be removed after submission, but ff also has a "loading in" status.
However, the solution is very simple, using setTimeout to set a delay, iframe to complete the execution.
So this is how _removeIframe is called in dispose:
 
if ( $$B.firefox ) { 
setTimeout($$F.bind(this._removeIframe, this), 0); 
} else { 
this._removeIframe(); 
} 

As for the removal of form, it is easier to do so in _removeForm:
 
var form = this._form, parent = form.parentNode; 
if ( parent ) { 
parent.insertBefore(this.file, form); parent.removeChild(form); 
} 
this._form = this._inputs = null; 

Judge 1 for parentNode, otherwise the following will be executed incorrectly if parentNode does not exist.

【 reset of file 】

In the example, there is an ResetFile function that resets the file control.

The way to reset file control 1 is to make the form you are in perform reset, but the problem is that it resets the other form controls as well.
Previously, value of file was not allowed to be modified due to security issues.
But now ff, chrome and safari can reset it by setting it to null:

file.value = "";
Of course other values are not allowed.
ps: I can't remember what I used to do.

For opera, there is a workaround that takes advantage of its type property:

file.type = "text"; file.type = "file";
By modifying the file control obtained by type, value is automatically restored to the null value, thus indirectly empting the file control.
ps: you can get the file path indirectly using this method, but since the value is cleared after the change, it is not useful.

The ie form control's type setting is not allowed to be modified, so opera cannot be used.
However, there are some solutions:
1. Create a new form, insert file into reset, and then remove it:
 
with(file.parentNode.insertBefore(document.createElement('form'), file)){ 
appendChild(file); reset(); removeNode(false); 
} 

The advantage is the use of native reset, which is stable and reliable but inefficient.
ps: removeNode is only supported by ie and opera. If you need compatibility, you can use removeChild instead.

2. Rebuild an file control using outerHTML:

file.outerHTML = file.outerHTML;
The benefit is efficiency, but because it is a newly created file control, everything previously associated is lost.
ps: ff does not support outerHTML.

3. Copy an file control using cloneNode:

file.parentNode.replaceChild(file.cloneNode(false), file);
Same as the previous one, but less efficient.

4. Select the text field of file control with select method, and then clear it:

file.select(); document.selection.clear();
or

file.select(); document.selection.clear();
This seems fine, but file must be able to be select.
ps: both methods can only be used on ie.

Since file needs to be associated in the program, methods 2 and 3 are not available.
Method 4 also looks good, but there is a fatal problem in ie testing the following code:
 
<form><input id="test" name="file" type="file"></form> 
<script> 
document.getElementById("test").onchange = function(){ 
this.select(); document.selection.clear(); 
this.form.submit(); 
} 
</script> 

When it is executed to submit, an "access denied" error will be displayed, for reasons that are not clear, whether ie intended or bug.

It seems that only method 1 can be used:
 
function ResetFile(file){ 
file.value = "";//ff chrome safari 
if ( file.value ) { 
if ( $$B.ie ) {//ie 
with(file.parentNode.insertBefore(document.createElement('form'), file)){ 
appendChild(file); reset(); removeNode(false); 
} 
} else {//opera 
file.type = "text"; file.type = "file"; 
} 
} 
} 

ps: let me know if there's a better way.

This function is not generic enough, and it is best to choose the method you want based on the actual situation.

Use skills

[number of files uploaded]

In the file upload instance, each file is uploaded at the same time.
After testing, the number of files that the browser can upload at the same time is as follows:
ie 2
ff 8
opera 8
chrome 6
safari 6
Since ie can only upload 2 files at most at the same time, more files can only be queued and cannot be uploaded at the same time.
ps: just eyeballing the results.

[passing parameters]

In the upload file instance, the corresponding modified file name can be passed, and the corresponding file name can also be found when multiple files are uploaded using "1 type upload".
Because the form control values are passed to the background, the order in which the data is obtained is 1 in the order of the previous table form control.
Just make sure the foreground file control is in the same order as the corresponding form control and you can take advantage of this feature to get the corresponding value.
Refer to the background code for details.

[callback function]

There are two methods to complete the callback function in response to the upload.
One is to output and execute the callback function in iframe after the background upload is completed or to call the callback function of the parent window through parent.
This is convenient, but you must do the processing in iframe, such as file properties viewing instances.
The other is to perform the callback function in iframe's onload.
The advantage is that you can put all the processing in the parent window, and iframe can do no processing or feed back information.
The downside is that there are compatibility issues and there are cases where onload is not triggered after loading (as explained in the iframe section above).
In the uploaded file instance, the data output in onFinish is processed in iframe.
Since there may be some unexpected circumstances that lead to a long response or even no response, 1 must set timeout in case of 10000.

[processing returned data]

As mentioned above, data output in iframe can be processed in onFinish.
To obtain data from iframe's body, there are several ways:
iframe.contentWindow.document.body.innerHTML
iframe.contentDocument.body.innerHTML
window.frames[iframename].document.body.innerHTML
The first two are similar, the latter is easier, but ie doesn't support contentDocument, unfortunately.
The third is to use the frames object to get, notice that the object is directly the window object.
Since the program can obtain the iframe object directly, the first method is used.
However, one of the problems mentioned in the section of iframe is the problem of returning the error message page.
In the uploaded file instance, the output in iframe is the file information data in the form of json.
In onFinish, it is handled like this:
 
try{ 
var info = eval("(" + iframe.contentWindow.document.body.innerHTML + ")"); 
show(" Upload to complete "); 
}catch(e){ 
show(" Upload failed "); stop(); return; 
} 

Only if the correct json data is returned will it work, otherwise an error is thrown, indirectly excluding error messages such as 404.
ps: please feel free to suggest a better way.

[destruction procedure]

There are many dom operations in the program. It is better to execute dispose method once to destroy the program when it is no longer needed.
For example, after removing file, before closing a window, before submitting a form, before walking through a form element, and so on.
You can save resources, prevent memory leaks in dom, and avoid conflicts when the forms are nested.

[usability]

After reading "ppk on javascript", I put more emphasis on usability.
The uploaded instance can be uploaded even if the browser does not support js. You can test it yourself.

[code]

Last 1 did not refresh upload system, a lot of people after uploading the file name after the reflection of garbled code, later found that is the coding problem.
When Chinese information is transmitted, it should be noted that the front and back code must be unified, including the configuration of charset, file code, web.config, etc.

[asp version]

The asp version has the same functionality as the.net version, which USES componentless upload classes.
However, there is a defect in the upload class itself, which causes errors when submitting the file control with the same name. After modification, it can now be used normally.


Directions for use

When instantiating, the first necessary parameter is the file control object:

new QuickUpload(file);

The second optional parameter is used to set the default properties of the system, including
Property: default value // description
parameter: {},// parameter object
action: "",// set action
timeout: 0,// set timeout (seconds are units)
onReady: function(){},// when the upload is ready
onFinish: function(){}, when the upload is complete
onStop: function(){}, when the upload stops
onTimeout: function(){}// upload timeout

The following methods are also provided:
upload: perform upload operation;
stop: stop uploading;
dispose: destruction procedure.


Program source code
 

var QuickUpload = function(file, options) { 

this.file = $$(file); 

this._sending = false;// Are you uploading  
this._timer = null;// The timer  
this._iframe = null;//iframe object  
this._form = null;//form object  
this._inputs = {};//input object  
this._fFINISH = null;// Complete execution function  

$$.extend(this, this._setOptions(options)); 
}; 
QuickUpload._counter = 1; 
QuickUpload.prototype = { 
// Set default properties  
_setOptions: function(options) { 
this.options = {// The default value  
action: "",// Set up the action 
timeout: 0,// Set the timeout ( seconds ) 
parameter: {},// Parameter object  
onReady: function(){},// Upload ready to execute  
onFinish: function(){},// This is done when the upload is complete  
onStop: function(){},// Execute when upload stops  
onTimeout: function(){}// The upload timeout is executed  
}; 
return $$.extend(this.options, options || {}); 
}, 
// Upload a file  
upload: function() { 
// Stop on 1 Time to upload  
this.stop(); 
// No file returned  
if ( !this.file || !this.file.value ) return; 
// May be in onReady Modify the relevant properties so put first  
this.onReady(); 
// Set up the iframe,form And form controls  
this._setIframe(); 
this._setForm(); 
this._setInput(); 
// Set the timeout  
if ( this.timeout > 0 ) { 
this._timer = setTimeout( $$F.bind(this._timeout, this), this.timeout * 1000 ); 
} 
// To upload  
this._form.submit(); 
this._sending = true; 
}, 
// Set up the iframe 
_setIframe: function() { 
if ( !this._iframe ) { 
// create iframe 
var iframename = "QUICKUPLOAD_" + QuickUpload._counter++, 
iframe = document.createElement( $$B.ie ? "<iframe name=\"" + iframename + "\">" : "iframe"); 
iframe.name = iframename; 
iframe.style.display = "none"; 
// Record completion procedures for easy removal  
var finish = this._fFINISH = $$F.bind(this._finish, this); 
//iframe Execute the completion program after loading  
if ( $$B.ie ) { 
iframe.attachEvent( "onload", finish ); 
} else { 
iframe.onload = $$B.opera ? function(){ this.onload = finish; } : finish; 
} 
// insert body 
var body = document.body; body.insertBefore( iframe, body.childNodes[0] ); 

this._iframe = iframe; 
} 
}, 
// Set up the form 
_setForm: function() { 
if ( !this._form ) { 
var form = document.createElement('form'), file = this.file; 
// Set properties  
$$.extend(form, { 
target: this._iframe.name, method: "post", encoding: "multipart/form-data" 
}); 
// Set the style  
$$D.setStyle(form, { 
padding: 0, margin: 0, border: 0, 
backgroundColor: "transparent", display: "inline" 
}); 
// Remove before submission form 
file.form && $$E.addEvent(file.form, "submit", $$F.bind(this.dispose, this)); 
// insert form 
file.parentNode.insertBefore(form, file).appendChild(file); 

this._form = form; 
} 
//action It may be modified  
this._form.action = this.action; 
}, 
// Set up the input 
_setInput: function() { 
var form = this._form, oldInputs = this._inputs, newInputs = {}, name; 
// Set up the input 
for ( name in this.parameter ) { 
var input = form[name]; 
if ( !input ) { 
// If there is no correspondence input new 1 a  
input = document.createElement("input"); 
input.name = name; input.type = "hidden"; 
form.appendChild(input); 
} 
input.value = this.parameter[name]; 
// Record the current input 
newInputs[name] = input; 
// Delete existing record  
delete oldInputs[name]; 
} 
// Remove the useless input 
for ( name in oldInputs ) { form.removeChild( oldInputs[name] ); } 
// Save the current input 
this._inputs = newInputs; 
}, 
// Stop uploading  
stop: function() { 
if ( this._sending ) { 
this._sending = false; 
clearTimeout(this._timer); 
// reset iframe 
if ( $$B.opera ) {//opera By setting the src There will be a problem  
this._removeIframe(); 
} else { 
this._iframe.src = ""; 
} 
this.onStop(); 
} 
}, 
// Destruction of program  
dispose: function() { 
this._sending = false; 
clearTimeout(this._timer); 
// remove iframe 
if ( $$B.firefox ) { 
setTimeout($$F.bind(this._removeIframe, this), 0); 
} else { 
this._removeIframe(); 
} 
// remove form 
this._removeForm(); 
// remove dom associated  
this._inputs = this._fFINISH = this.file = null; 
}, 
// remove iframe 
_removeIframe: function() { 
if ( this._iframe ) { 
var iframe = this._iframe; 
$$B.ie ? iframe.detachEvent( "onload", this._fFINISH ) : ( iframe.onload = null ); 
document.body.removeChild(iframe); this._iframe = null; 
} 
}, 
// remove form 
_removeForm: function() { 
if ( this._form ) { 
var form = this._form, parent = form.parentNode; 
if ( parent ) { 
parent.insertBefore(this.file, form); parent.removeChild(form); 
} 
this._form = this._inputs = null; 
} 
}, 
// Timeout function  
_timeout: function() { 
if ( this._sending ) { this._sending = false; this.stop(); this.onTimeout(); } 
}, 
// Complete the function  
_finish: function() { 
if ( this._sending ) { this._sending = false; this.onFinish(this._iframe); } 
} 
} 

Full instance download
Full instance download (asp version)
Related applications: JavaScript image upload preview effect
Reprint please indicate the source: http: / / www cnblogs. com/cloudgamer /

Related articles: