Feb 1, 2011
File upload simil Ajax
Esto no es nada de HTML5 ni Flash, solo una forma de hacer un uploader de archivos asincrónico sin recargar la página. Y es que aveces hacemos una buena interface en ajax con todo bonito y cuando tenemos que subir un archivo, nos encontramos con el problema de que no podemos hacer upload de un archivo por Ajax, ya que no tenemos permisos desde Javascript para leer un archivo, pero igual hay una solución.
Esto lo tube que hacer en un panel de configuración donde se tenían que poder subir archivos de forma asincrónica (mas de uno a la vez), por lo que tube que rebuscarmelas.
En fin, la única forma de subir un archivo (sin html5 ni flash) es, con un input del tipo “file” y enviarlo por post. Cosa que podemos hacer de una forma censilla enviando los datos de un formulario real a un iframe, que obviamente puede ser invisible. Por lo que lo pasos a hacer son:
- Tener un formulario real con un input de tipo file
- Crear un iframe invisible
- Establecer el target del formulario a ese iframe
- Hacer un submit()
- Esperar que termine de subir el archivo
- Leer el contenido del iframe para saber que paso
Entonces como dijimos lo primero va ser crear un formulario real:
<form id="uploadform" action="/upload-ajax.php" method="post" enctype="multipart/form-data">
<label for="file">Archivo</label>:
<input type="file" name="file" />
<input type="button" value="subir" onclick="subirArchivo()" />
</form>
Un vez esto, en la función subirArchivo(), crearemos el iframe y enviaremos nuestro archivo a el:
subirArchivo = function () {
// Creamos el iframe
var ifm = document.createElement('iframe');
// lo hacemos "invisible"
ifm.style.display = 'none';
// le definimos un nombre (importante)
ifm.name = 'tmpfrm';
// seteo que va a pasar al cargarse (esto lo vemos mas adelante)
ifm.onload = function (e) {}
// Seleccionamos el formulario
var eform = document.getElementById('uploadform');
// le definimos como target el nombre del iframe (nombre, no id)
eform.target = 'tmpfrm';
// selecionamos el elemento body
var ebody = document.getElementsByTagName('body')[0];
// metemos el iframe en el body:
ebody.appendChild(ifm);
// enviamos el archivo:
eform.submit();
}
Y obviamente en el evento de “cargado” del iframe, verificamos que este todo bien:
...
ifm.onload = function (e) {
var respuesta = ifm.contentWindow.document.body.innerHTML;
if (respuesta == "ok") {
alert ("Archivo subido");
} else {
alert ("hubo un error al subir el archivo");
}
}
...
Eso nos serviría para subir un archivo por vez, si queremos hacer algo mas dinámico, les dejo algo para que lo estúdien.
<h2>Subir arhivos:</h2> <ul id="filelist"></ul> <input type="button" id="btn_addfile" value="Agregar archivo" />
window.onload = function (e) {
var uploadLastId = 0;
var btnadd = document.getElementById('btn_addfile');
btnadd.onclick = function (e) {
var eul = document.getElementById('filelist');
var eframe = document.createElement('iframe');
var eli = document.createElement('li');
var eform = document.createElement('form');
var efile = document.createElement('input');
var esubmit = document.createElement('input');
eframe.style.display = 'none';
eframe.name = "ifmupload"+uploadLastId;
eframe.onload = function (e) {
var respuesta = e.originalTarget.contentWindow.document.body.innerHTML;
if (respuesta == "ok") {
e.originalTarget.parentNode.innerHTML = "Archivo subido";
} else {
e.originalTarget.parentNode.innerHTML = "Error al subir archivo";
}
}
eform.action = "/upload-ajax.php";
eform.method = "post";
eform.enctype = "multipart/form-data";
eform.target = eframe.name;
eform.onsubmit = function (e) {
esubmit.disabled = true;
esubmit.value = "subiendo...";
}
efile.type = 'file';
efile.name = 'file';
esubmit.type = 'submit';
esubmit.value = "Subir";
eform.appendChild(efile);
eform.appendChild(esubmit);
eli.appendChild(eframe);
eli.appendChild(eform);
eul.appendChild(eli);
uploadLastId++;
}
}
Tips relacionados:

Como se podria leer el contenido del ifram para poder leer los datos>?>>
Twitter: exos
says:
Juestamente es lo que hacemos aca:
ifm.contentWindow.document.body.innerHTML;
En la propiedad contentWindow del elemento Iframe se encuentra su document, del cual seleccionamos el body y vemos el innerHTML.
sea lo que sea que se devuelva (no hace falta que exista la etiqueta body);
De igual manera te aclaro que esto no es cross-domain, osea, no se puede leer el contenido de un iframe que apunta hacia otro dominio.
Gran post! me ha venido muy bien, gracias!
A modo de comentario y sin ofender: Aunque tu sintaxis en C# es perfecta tu “sintaxis” del español es regular, “tube” sólo existe con b en “youtube”
Twitter: exos
says:
Tuve con B o hecho sin h son errores comunes, pasa que escribo mas en javascript/php que en español, y vamos a decirlo, el español esta bugeado, ya con que v y b suenen iguales es un error de diseño, que tiende a los errores en la implementación.
Por otro lado, gracias por tu post, pero no hay nada escrito en C#, esto es puro javascript.
me gustaría que explicaras un poco más este paso, dado que no se donde va.. y estoy necesitando implementar algo como esto y hace días que ando buscando y no me funcionan los que yo tengo.
…
ifm.onload = function (e) {
var respuesta = ifm.contentWindow.document.body.innerHTML;
if (respuesta == “ok”) {
alert (“Archivo subido”);
} else {
alert (“hubo un error al subir el archivo”);
}
}
…
Por otro lado, conoces alguna forma de validar el formato del archivo desde JS?? por que así me evito hacer todo esto, sin mostrar el error en otra página. (en caso de que esto no me funcione)
Saludos y gracias!!
Lo de validar el archivo ya lo estoy haciendo desde js,igual me gustaría saber lo otro.
Saludos
Twitter: exos
says:
En donde hace:
ifm.onload = function (e) {
var respuesta = ifm.contentWindow.document.body.innerHTML;
if (respuesta == “ok”) {
alert (“Archivo subido”);
} else {
alert (“hubo un error al subir el archivo”);
}
}
Lo que hago es definiendo ifm.onload (que es el evento de cuando termina de cargar el iframe) como una función que lee el contenido del iframe para determinar si el archivo fue aceptado o no, en este caso desde el script en php que resivo el archivo devolveria una “ok”, pero lo podes hacer como mas te gusta.
Twitter: exos
says:
Hu me olvidé, sobre lo de validar el formato, en realidad como validar el formato real del archivo no vas a poder hasta que lo puedas leer (osea hasta que este subido), lo que si podés validar es la extensión de este. Para eso podés hacer en el submit del form:
eform.onsubmit = function (e) {
esubmit.disabled = true;
esubmit.value = “subiendo…”;
}
Podrías poner un condicional primero, con una expresión regular, si la función devuelve false, el submit se cancela:
eform.onsubmit = function (e) {
if (!/\.(png|jp[e]?g|gif|bmp)$/i.test(efile.value)) {
alert (“Formato de archivo no válido”);
return false;
}
esubmit.disabled = true;
esubmit.value = “subiendo…”;
}
En este caso solo te dejaria subir archivos con extensión png, jpg, jpeg, gif y bmp.
solo como comentario en la parte, si necesitan subir varios archivos usando esta funcion … asegurense de cambiar esta parte:
ifm.name = ‘tmpfrm’;
ya que provocara detalles al momento de hacer el onload … una solucion que tome fue poner una variable y concatenarsela al final de esta forma:
cns++;
ifm.name = ‘tmpfrm’+cns;
Saludos a todos …
P.D. Me sirvio muchisimo el ejemplo, muchas gracias
Twitter: exos
says:
Gracias por la corrección Daniel H!
Hola, Exos.
El archivo se sube correctamente solo que siempre me aparece el alert (“hubo un error al subir el archivo”)
desde php hago un echo “ok”; pero el iframe no lo recibe. Cual puede ser el error que estoy cometiendo?
Saludos y gracias!!
Noelia
Twitter: exos
says:
Fijate si e.originalTarget.contentWindow.document.body.innerHTML te devuelve vasio o que, en que browser lo estas probando?
Hay otra forma mejor para comprobarlo que seria, crear un identificador, y al terminar de subir el iframe grabar una variable de session, ejemplo:
en upload-ajax.php
$_SESSION['uploadof' + $id ] = true;
y otro file upload-check.php
if (isset($_SESSION['uploadof' + $id]) && $_SESSION['uploadof' + $id ]) {
print “ok”;
} else {
print “fail”;
}
y en el onload del iframe hacer una peticion por ajax:
eframe.onload = function (e) {
peticionAjax(‘upload-check.php’, function (respuesta) {
if (respuesta == “ok”) {
// ok;
} else {
// fail;
}
}
}
Se entiende?