¿Cómo importar/subir archivos con jQuery AJAX?

1 nov 2012, 15:08:14
Vistas: 66.2K
Votos: 0

Sigo teniendo un problema al agregar el(los) archivo(s) en AJAX para que la función ajax del servidor pueda procesar los datos. ¿Cómo puedo hacer que $_FILES se pase igual que con la acción predeterminada (integrada en los elementos del formulario)?

Formulario HTML

<form id="frmImport" name="frmImport" method="post" enctype="multipart/form-data" >
    <input id="file_import" name="importData" type="file" />
    <br/>
    <button id="btn_import" type="submit" >Importar</button>
</form>

JavaScript

    jQuery(document).ready(function($) {
    //Esto ya está configurado y enviado desde el lado del servidor, y se
    // usa para evitar que usuarios no autorizados suban datos.
    var importnonce = "3x4mpl3f4k3n0nc3"; 

    $('#frmImport').submit(function(e) {
        e.preventDefault();

        // COMPROBAR SI HAY ERRORES AQUÍ.
        // SI EXISTEN ERRORES, DEVOLVER FALSE PARA FINALIZAR OPERACIONES.

        var formData = new FormData();
        formData.append('action', 'ajax_handler_import');
        formData.append('_ajax_nonce', importNonce);

        // El problema ocurre aquí. PHP obtiene un string '[object FormData]'.
        var importFiles = $('#file_import')[0].files;
        formData.append('uploadFiles', importFiles);

        jQuery.ajax({
            url: ajaxurl,
            type: 'POST',
            cache: false,
            contentType: false,
            processData: false,

            data: formData,

            beforeSend: function(jqXHR, settings) {
                console.log("Aún no se ha entrado al lado del servidor.");
            },
            dataFilter: function(data, type) {
                // Usado para analizar datos y posiblemente verificar errores.
                console.log("Cadena JSON devuelta desde el lado del servidor.");
            },
            success: function(data, textStatus, jqXHR) {
                console.log("¡De vuelta del lado del servidor!");
                // Verificando errores que pueden haber sido capturados en el lado del servidor que
                // normalmente no se mostrarían en la función de error de Ajax.
                if (data.msg != 'success') {
                    alert(data.error);
                }
            },
            error: function(jqXHR, textStatus, errorThrown) {
                console.log("Ha ocurrido un error de JS.");
            },
            complete: function(jqXHR, textStatus) {
                console.log("Ajax ha terminado.");
            }
        });

        return false;
    });
});

PHP Servidor/Manejador

<?php
class server {
    function ajax_import_handler() {
        check_ajax_referer("ajax_handler_import");

        $rtnData = new stdClass();
        $rtnData->msg = 'success'; //Para señalar errores del Servidor
        $rtnData->error = ''; //Para mostrar mensajes de error personalizados o usar el método try/catch

        //Hacer cosas
        foreach ($_FILES as $key => $value) {
            //OBTENER CONTENIDO DEL ARCHIVO
            $file_array[$key] = json_decode(file_get_contents($value['tmp_name']));
        }

        // Hacer más cosas con el(los) archivo(s).

        // Agregar cosas a $rtnData.

        echo json_encode($rtnData);
    }
}
?>
0
Todas las respuestas a la pregunta 3
2

No lo he probado, pero creo que necesitas añadir el objeto FormData directamente como parámetro data. Algo así:

    var ajaxData = new FormData();

    ajaxData.append( 'action', 'ajax_handler_import' );
    ajaxData.append( '_ajax_nonce', importNonce );
    // o quizás omitir el nonce por ahora

    jQuery.each($('#fileImportData')[0].files, function(i, file) {
        ajaxData.append('file-'+i, file);
    });

El resto de tu código puede permanecer igual

1 nov 2012 15:33:21
Comentarios

Disculpa no haber respondido antes... Estaba intentando terminar un proyecto y pensé en volver cuando tuviera más conocimiento sobre el tema. Gracias por proporcionar la solución, no sabía que añadir el objeto FormData a otro objeto lo estropearía, y me estaba quedando sin ideas. Claro que sería un problema simple xP. La lección aprendida aquí: mantente alejado de objetos mixtos.

EkoJR EkoJR
1 dic 2012 11:44:29

Por no mencionar que esto no funcionará en IE9 y versiones anteriores de IE.

Arda Arda
3 nov 2015 17:32:49
2

Ajax en el sentido tradicional es XMLHttpRequest, que no te permite codificar y enviar archivos locales a un servidor.

Las formas comunes de hacer cargas mediante métodos "ajax" son: usar un archivo Flash swf para manejar la carga en la misma página, o usar un formulario que tenga como destino un iframe invisible de 1x1.

Aquí hay una pregunta muy similar con una buena respuesta para ver cómo puedes hacerlo

1 nov 2012 15:51:04
Comentarios

Sí, estaba revisando ese Q/A recientemente. Lamentablemente, no estoy familiarizado con Flash, y he estado tratando de evitar agregar plugins siempre que sea posible. Principalmente he estado intentando seguir el método Ajax de WordPress, pero por lo que he encontrado, simplemente pasarlo a ajax está restringido/o limitado por los navegadores. Principalmente refiriéndome a este Q/A me llevó a Objetos FormData que dice que es posible si el navegador utilizado lo tiene.

EkoJR EkoJR
2 nov 2012 10:45:07

Resulta que puedes usar tus propios métodos para pasar datos de archivos a una función AJAX en lugar de depender de un plugin JS. En su mayor parte, los objetos FormData (como se mencionó anteriormente), manejan esto con operaciones XMLHttpRequest Level 2 (según mi entendimiento actual), y es compatible con todos los navegadores principales ahora. Supuse que sería cuestión de tiempo después de que saliera HTML5. Sin embargo, usar plUpload es una recomendación posible para datos de archivos grandes.

EkoJR EkoJR
1 dic 2012 11:54:53
0

Aquí está la solución que pude encontrar, y en realidad veo varios errores comunes que se cometieron.

  • Los archivos no se estaban adjuntando correctamente en JS.
  • El AJAX dataFilter: function() podría reemplazarse con dataType: 'json'.
  • El envío de JS no necesita retornar false para detener la acción del formulario.
    • Podría reemplazarse con event.stopPropagation() y e.preventDefault().
    • Añadir una acción de formulario vacía en HTML.
  • La función PHP para AJAX no terminaba con die().

HTML

<form id="form_import" name="form_import" method="post" enctype="multipart/form-data" action >
    <input id="file_import" name="import_data" type="file" />
    <br/>
    <button id="btn_import" type="submit" >Importar</button>
</form>

JavaScript

jQuery(document).ready(function($){
    // Los archivos PHP podrían mostrar un Nonce. Sin embargo, esto localiza los scripts.
    var importNonce = localizedData.import_nonce;

    $('#form_import').submit( function( event ) {
        event.stopPropagation(); // Detiene eventos
        event.preventDefault(); // Detiene completamente los eventos

        var formData = new FormData();
        var importFiles = $('#file_import')[0].files;

        // Para CADA archivo, adjuntar a formData.
        // NOTA: Adjuntar todos los importFiles no se transfiere bien a PHP.
        jQuery.each( importFiles, function( index, value ) {
            var name = 'file_' + index;
            formData.append( name, value )
        });

        formData.append( 'action', 'ajax_file_import' );
        formData.append( '_ajax_nonce', importNonce );

        jQuery.ajax({
            url: ajaxurl,
            type: 'POST',
            data: formData,
            cache: false,
            dataType: 'json', // Esto reemplaza dataFilter: function() && JSON.parse( data ).
            processData: false, // No procesar los archivos
            contentType: false, // Establecer el tipo de contenido como falso ya que jQuery le dirá al servidor que es una solicitud de cadena de consulta


            beforeSend: function( jqXHR, settings ){
                // (OPCIONAL) Concepto alternativo de AJAX.
                // Elimina el antiguo IFrame si existe.
                var element = document.getElementById('import_IF');
                if ( element !== null ){
                    element.parentNode.removeChild( element );
                }
                // FIN (OPCIONAL).
            },
            success: function( data, textStatus, jqXHR ) {
                console.log( 'Respuesta de la función/método PHP AJAX.' );

                // Hacer algo con data.values

                // (OPCIONAL) Concepto alternativo de AJAX.
                // Necesario para descargar en AJAX.
                var paramStr = '';
                paramStr += '?_ajax_nonce=' + data._ajax_nonce;
                paramStr += '&action='      + data.action;
                paramStr += '&filename='    + data.filename;

                var elemIF = document.createElement("iframe");
                elemIF.id = 'import_IF'
                elemIF.style.display = "none";
                elemIF.src = ajaxurl + paramStr;

                document.body.appendChild(elemIF);
                elemIF.parentNode.removeChild(elemIF);
                // FIN (OPCIONAL).
            },
            complete: function(jqXHR, textStatus){
                console.log( 'AJAX ha finalizado.' );

                // (OPCIONAL) Concepto alternativo de AJAX.
                // Limpiar IFrame.
                var element = document.getElementById('import_IF');
                if ( element !== null ){
                    element.parentNode.removeChild( element );
                }
                // FIN (OPCIONAL).
            }
        });// Fin AJAX.
    });// Fin .submit().
});

PHP

<?php
class server {
    public function __construct() {
        // Ejemplo para añadir script.
        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_example' ) );
        add_action( 'wp_ajax_ajax_file_import', array( $this, 'ajax_import_example' ) );
    }

    public funtion enqueue_example() {
        wp_register_script(
            'example-js',
            FILE_URL,
            array(),
            VERSION,
            false
        );
        wp_enqueue_script( 'example-js' );

        $data = array(
            'import_nonce'  => wp_create_nonce( 'ajax_file_import_nonce' ),
        );

        wp_localize_script( 'example-js', 'localizedData', $data );
    }

    public function ajax_import_example() {
        check_ajax_referer( 'ajax_file_import_nonce' );

        $raw_content = array();
        $i = 0;
        while ( isset( $_FILES[ 'file_' . $i ] ) ) {
            $file_arr = $_FILES[ 'file_' . $i ];
            $file_content = file_get_contents( $file_arr['tmp_name'] );
            $raw_content[]  = json_decode( $file_content );
            $i++;
        }

        $new_data = array();
        // Hacer algo con los nuevos datos.
        update_option( 'example_database', $new_data );

        // (OPCIONAL) Añadir datos para retornar a AJAX Success
        $rtn_data = array(
            'action'               => 'apl_import',
            '_ajax_nonce'          => wp_create_nonce( 'alt_ajax' ),
        );
        echo json_encode( $rtn_data );
        // FIN (OPCIONAL).

        die();
    }
}
?>
2 jul 2017 10:41:15