1

I'm building a support contact form where the user can upload files. The file upload is managed using AJAX: the user can upload the files, then submit the form at their convenience. The current layout that works is, however, not aesthetic: the upload input is below the form submit button.

I read about nested forms and the new form attribute and I thought this would do the trick:

<form action="" method="post" enctype="multipart/form-data" id="main-form">
...
    <form action="/upload_file_ajax.php" method="post" id="file-upload-form">
    <div class="form-group row mb-3 mb-3">
        <label class="col-sm-3 col-lg-3 col-form-label" for="file"><?php echo $label['attach-file']; ?></label>
        <div class="col-sm-8 col-lg-7">
            <input class="form-control custom-file-input" name="file" id="file" type="file" form="file-upload-form" />
        </div>
    </div>
    </form>

    <div class="form-group row">
        <div class="col-4 offset-3">
            <button type="submit" name="submit" class="btn btn-outline-success" form="main-form"><?php echo $label['submit-button']; ?></button>
        </div>
    </div>

    </form>

I have added the form attribute to every input and button. However, the inner form ("file-upload-form") won't submit at all when I add the file.

Could it be because this is an auto-submit input, i.e. the Javascript triggers the AJAX when the file is selected? This is the trigger line:

$('#file-upload-form').on('change', function(e){
...

As soon as I move the nested form below the closing </form> tag of the main form, it works.

If the aesthetic layout can be achieved in any other way, e.g. the file upload input can appear above the Submit button without nesting the forms, please let me know.

This layout works but unaesthetic

This one looks nice but doesn't work

EDIT: This is the revised Javascript that takes care of the file upload via AJAX. I have removed the inner form tags as advised but the input still won't submit.

$(function(){
    // listen for input changes (when a file is selected)
    $('#file-upload-input').on('change', function(e){
        //var formData = new FormData();
        // file has been selected, submit it via ajax
        $.ajax({
            type: 'POST',
            url: "/upload_file_ajax.php",
            data: new FormData(this),
            cache: false,
            contentType: false,
            processData: false,
            success: function(data){
                // the upload_file_ajax.php endpoint returns the file name and a status message for the uploaded file
                console.log(data.filename, data.message);
                // we then inject these into the main data form
                var $hiddenInput = $('<input type="hidden" name="uploads[]" value="'+data.filename+'">');
                $('#main-form').append($hiddenInput);

                // show a thumbnail maybe?
                var $thumbnail = $('<img src="/uploaded_files/'+data.filename+'" width="40" height="40" />');
                $("#preview").append($thumbnail);

                $("#status").html(JSON.stringify(data.message));

                // reactivate file upload form to choose another file
                $('#file-upload-input').val('');
            },
            error: function(){
                console.log("error");
            }
        });
    });
}); 

This is what the revised HTML looks like:

<form action="" method="post" enctype="multipart/form-data" id="main-form">
... (other inputs here)...
    <div class="form-group row offset-3 mb-3">
        <div class="col-12" id="preview"></div>
        <div class="col-12" id="status"></div>
    </div>

    <div class="form-group row mb-3 mb-3">
        <label class="col-sm-3 col-lg-3 col-form-label" for="file"><?php echo $label['attach-file']; ?></label>
        <div class="col-sm-8 col-lg-7">
            <input class="form-control custom-file-input" name="file" id="file" type="file" id="file-upload-input" form="file-upload-form" />
        </div>
    </div>

    <div class="form-group row">
        <div class="col-4 offset-3">
            <button type="submit" name="submit" class="btn btn-outline-success" form="main-form"><?php echo $label['submit-button']; ?></button>
        </div>
    </div>

    </form>
cheeseus
  • 369
  • 3
  • 21
  • I guess I'm not sure why you need to wrap the file uploaders in a form. Why not just use JavaScript to handle your ajax request on button click? – isherwood Jun 09 '22 at 14:04
  • 1
    @isherwood, because I thought this was required. If it's not, then I can remove the inner `form` tags and amend the Javascript/Ajax. – cheeseus Jun 09 '22 at 14:07
  • @isherwood, I guess I didn't amend the jQuery/AJAX correctly because the file upload input won't submit... I'll add the entire code in my post above. – cheeseus Jun 09 '22 at 14:13
  • No need for nested forms (which are invalid in HTML; don't know where you read they were okay), just use any of the vast numbers of questions about multiple uploads on Stack Overflow. – Heretic Monkey Jun 09 '22 at 14:14
  • @HereticMonkey, this is where I read it: https://stackoverflow.com/a/28528359/1754033. I've been reading about multiple uploads here and elsewhere for the past three days. That's the best I could come up with. – cheeseus Jun 09 '22 at 14:16
  • Does this answer your question? [Multiple files select and upload](https://stackoverflow.com/questions/8669579/multiple-files-select-and-upload) – Heretic Monkey Jun 09 '22 at 14:17
  • That's not nested forms. That's two separate forms, with inputs associated with them separately via the `form` attribute. – Heretic Monkey Jun 09 '22 at 14:18
  • @HereticMonkey, no, that thread does not answer my question. I don't want to submit multiple files simultaneously. I want to be able to upload the files one by one and at a later moment, when all the other fields are complete, to press the Submit button. – cheeseus Jun 09 '22 at 14:22

1 Answers1

0

Here is how I solved my problem. I found the answer here, on SO, but can't find the link to the post any more.

The problem with uploading a file independently, without submitting the form or without having <form>...</form> tags, is that FormData(); does not contain the file as it does when the <form>...</form> tags are present. So, you need to append the file to it.

Here is my entire jQuery code that takes care of the file upload. On success, it creates additional form input tags containing the uploaded files info, so that I can submit them together with the form. It also creates a thumbnail for each uploaded image, and a Delete button next to the input in case the user changes their mind.

$('#file-upload-input').change(function(){
    var file_data = $('#file-upload-input').prop('files')[0];
    var form_data = new FormData();
    // pass the file itself – needed because the input is submitted without <form> tags
    form_data.append('file', file_data);
    // pass website language variable to the PHP processor to load the correct language file
    form_data.append('lang', '<?php echo $lang; ?>');
    $.ajax({
        url: "/ajax_upload_file.php",
        type: "POST",
        data: form_data,
        cache: false,
        contentType: false,
        processData: false,
        success: function(data){
            // the upload_file_ajax.php endpoint returns the file name and a status message for the uploaded file
            console.log(data.filename, data.message);
            // we then inject these into the main data form
            var $hiddenInput = $('<div class="input-group mb-1"><input class="form-control" readonly type="text" name="uploads[]" value="'+data.filename+'" /><input type="button" name="delete_'+data.filename+'" id="delete_'+data.filename+'" value="Delete" class="delete btn btn-outline-danger ms-2" /></div>');
            $('#uploaded_files').append($hiddenInput);
            // show a thumbnail if the uploaded file is an image
            var $thumbnail = $('<img src="/uploaded_files/'+data.filename+'" height="75" id="img_'+data.filename+'" class="me-1" />');
            $("#preview").append($thumbnail);
            // print a status message returned from the PHP processor
            $("#status").html(data.message);
            // reactivate file upload form to choose another file
            $('#file-upload-input').val('');
        },
        error: function(){
            console.log("error");
        }
    });
});

This is the relevant HTML. It contains divs for the inputs containing the uploaded files names, for the thumbnails (called "preview"), and for the status message returned from the PHP script.

    <div class="form-group row offset-2 mb-3">
        <div class="col-sm-8 col-lg-7" id="uploaded_files">
        </div>
    </div>

    <div class="form-group row offset-2 mb-3">
        <div class="col-12" id="preview"></div>
    </div>

    <div class="form-group row offset-2 mb-3">
        <div class="col-12" id="status"></div>
    </div>

    <div class="form-group row mb-3 mb-3">
        <label class="col-sm-3 col-lg-2 col-form-label" for="file"><?php echo $label['attach-file']; ?></label>
        <div class="col-sm-8 col-lg-7">
            <input class="form-control custom-file-input" name="file" type="file" id="file-upload-input" />
        </div>
    </div>
cheeseus
  • 369
  • 3
  • 21