Skip to main content

Pujada arxius Drag&Drop amb formulari standard

Tot i que estem fent un enviament de formulari estàndard (sense AJAX/Fetch), utilitzem JavaScript per "enllaçar" l'acció d'arrossegar fitxers amb un input de tipus file que tenim ocult.

La clau d'aquest sistema és l'objecte DataTransfer. Quan un usuari deixa anar fitxers sobre un element, JavaScript captura aquests fitxers i els assigna a la propietat .files del nostre input real. Així, quan l'usuari fa clic a "Upload", el formulari s'envia amb tots els fitxers "arrossegats".

Controlador i Ruta

La ruta apunta al mètode que carrega la vista, i el formulari enviarà les dades al mètode upload_multiple que ja hem creat anteriorment (el que gestiona userfile[]).

PHP
// Routes.php
$routes->get('files/dragupload', 'UploadFilesController::index_drag');

// UploadFilesController.php
public function index_drag() {
    return view('uploads/upload_form_drag', [
        'title'  => "Pujada Drag & Drop",
        'errors' => []
    ]);
}

Vista (upload_form_drag.php)

PHP
<?= $this->extend('layouts/news') ?>
<?= $this->section('news_content') ?>

<h2><?= esc($title) ?></h2>

<style>
    #dropContainer {
        border: 2px dashed #3498db;
        background: #f8fbff;
        border-radius: 10px;
        padding: 40px;
        text-align: center;
        transition: all 0.3s ease;
        color: #3498db;
        font-family: sans-serif;
        cursor: pointer;
    }
    /* Classe que s'activarà quan l'usuari estigui a sobre amb l'arxiu */
    #dropContainer.drag-over {
        border-color: #2ecc71;
        background-color: #e8f9ee;
        transform: scale(1.02);
    }
    #fileList {
        margin-top: 15px;
        text-align: left;
        font-size: 0.9em;
        color: #333;
    }
</style>

<div style='padding:25px'>
    
    <div id="dropContainer">
        <div id="dropMessage">Arrossega els teus fitxers aquí o fes clic per seleccionar</div>
        <div id="fileList"></div>
    </div>

    <?= form_open_multipart(base_url('files/upload_multiple'), ['id' => 'uploadForm']) ?>
        
        <input type="file" id="formFiles" name="userfile[]" multiple style="display:none" />

        <div style='padding-top:20px; text-align: center;'>
            <input type="submit" value="Pujar fitxers seleccionats" id="btnSubmit" disabled 
                   style="padding: 10px 30px; cursor: pointer;" />
        </div>
    <?= form_close() ?>

</div>

<script>
    const dropContainer = document.getElementById("dropContainer");
    const fileInput     = document.getElementById("formFiles");
    const dropMessage   = document.getElementById("dropMessage");
    const fileList      = document.getElementById("fileList");
    const btnSubmit     = document.getElementById("btnSubmit");

    // 1. Obrir el selector de fitxers si es fa clic a la zona
    dropContainer.addEventListener("click", () => fileInput.click());

    // 2. Prevenir comportament per defecte del navegador (obrir el fitxer)
    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
        dropContainer.addEventListener(eventName, e => {
            e.preventDefault();
            e.stopPropagation();
        }, false);
    });

    // 3. Efectes visuals en arrossegar
    ['dragenter', 'dragover'].forEach(eventName => {
        dropContainer.addEventListener(eventName, () => dropContainer.classList.add('drag-over'), false);
    });

    ['dragleave', 'drop'].forEach(eventName => {
        dropContainer.addEventListener(eventName, () => dropContainer.classList.remove('drag-over'), false);
    });

    // 4. El "cor" del sistema: Capturar el Drop
    dropContainer.addEventListener("drop", (e) => {
        const dt = e.dataTransfer;
        const files = dt.files; // Obtenim els fitxers arrossegats

        if (files.length > 0) {
            fileInput.files = files; // Assignem els fitxers a l'input ocult
            updateFileList(files);
        }
    });

    // 5. Capturar també si l'usuari tria fitxers pel mètode clic tradicional
    fileInput.addEventListener("change", () => {
        if (fileInput.files.length > 0) {
            updateFileList(fileInput.files);
        }
    });

    // Funció per mostrar a l'usuari què ha seleccionat
    function updateFileList(files) {
        let html = "<strong>Fitxers preparats:</strong><ul>";
        for (let i = 0; i < files.length; i++) {
            html += `<li>${files[i].name} (${(files[i].size / 1024).toFixed(2)} KB)</li>`;
        }
        html += "</ul>";
        
        fileList.innerHTML = html;
        dropMessage.innerHTML = "Fitxers carregats! Pots tornar a arrossegar per canviar-los.";
        btnSubmit.disabled = false; // Activem el botó de pujada
    }
</script>

<?= $this->endSection() ?>
  1. Input ocult: Utilitzem style="display:none" o visibility:hidden. L'important és que estigui dins del formulari. Aquest input té l'atribut multiple i el nom userfile[], essencial per a la pujada múltiple de CodeIgniter.

  2. e.preventDefault() i e.stopPropagation(): Són crítics. Si no els posem, quan l'usuari deixi anar una imatge, el navegador sortirà de la teva web i obrirà la imatge a tota pantalla.

  3. L'enllaç de dades: La línia fileInput.files = dt.files; és la que fa la màgia. Connecta el món del Drag & Drop de l'usuari amb el sistema de formularis de tota la vida.

  4. Feedback visual: Canviar el color de la vora (.drag-over) i llistar els noms dels fitxers és vital per a la UX (User Experience). L'usuari necessita saber que el sistema ha "entès" la seva acció abans de prémer el botó d'enviar.

En aquesta versió de formulari estàndard (sense AJAX), l'objecte DataTransfer.files substitueix els fitxers previs. És a dir, si l'usuari arrossega 2 fitxers i després n'arrossega 2 més, només se n'enviaran els 2 últims.