phplaraveldynamic-forms

How to handle multiple files selected in an unknown number of dynamically added file selects (PHP/Laravel)


I have a form to collect information about mechanical parts. The form is dynamic in that it allows users to "add another part" onto the form, which collects like information about potentially multiple parts.

One of the dynamic input fields is a file select, that allows for multiple files to be selected. <input type="file" name="dynamic[files][]" id="files" class="file form-control" multiple accept=.pdf, .doc, .stp, .step, .stl" />

I am struggling to "connect" the files that get uploaded, with the individual parts that are added.

Consider this example:

Part 1 has 2 files uploaded (File A & File B).

Part 2 has 3 files uploaded (File C, File D, & File E).

Part 3 has 2 files uploaded (File F & File G).

On the backend, as we are looping through the parts, you get an index/key of 0, 1, and 2. For Part 1, there should be 2 files to loop through. For Part 2, there should be 3 files to loop through. And for Part 3, there should be 2 files to loop through. However, the keys to the parts are trying to point to files of the same array key, which means there are more files than array keys for parts.

As you can see in this example, the foreach loop is seemingly unable to grab the files that are pertaining to the part currently being iterated through.

What am I doing wrong here?


My HTML Code:

    <div class="part-form">
        <h3 class="part-name">Part 1</h3>

        <div class="form-group">
            <label for="part_name">Part Name</label>
            <input type="text" name="dynamic[name][]" class="form-control part-name textbox"/>
        </div>

        <div class="form-group">
            <label for="project-quantity">Quantity Needed</label>
            <input type="number" name="dynamic[quantity][]" id="project-quantity" class="textbox form-control" min="1"/>
        </div>

        <div class="form-group">
            <label for="files">Upload File(s)</label>
            <input type="file" name="dynamic[files][]" id="files" class="file form-control" multiple accept=.pdf, .doc, .stp, .step, .stl" />
        </div>
    </div>

    <button type="button" class="dynamic-add" onclick="addDynamicForm()">Add Another Part</button>

My Javascript:

    <script type="text/javascript">
        function addDynamicForm() {
            var numParts = $(".part-form").size();
            var newNumber = numParts + 1;

            var form = `
                <div class="part-form">
                    <h3 class="part-name">Part `+newNumber+`</h3>

                    <div class="form-group">
                        <label for="part_name">Part Name</label>
                        <input type="text" name="dynamic[name][]" class="form-control part-name textbox"/>
                    </div>

                    <div class="form-group">
                        <label for="project-quantity">Quantity Needed</label>
                        <input type="number" name="dynamic[quantity][]" id="project-quantity" class="textbox form-control" min="1"/>
                    </div>

                    <div class="form-group">
                        <label for="files">Upload File(s)</label>
                        <input type="file" name="dynamic[files][]" id="files" class="file form-control" multiple accept=".pdf, .doc, .stp, .step, .stl" />
                    </div>
                </div>
            `;

            $(".part-form").last().after(form);
        }
    </script>

My backend Laravel/PHP:

    $returnString = "";
    $dynamicFields = $request->input('dynamic');
    $dynamicFiles = $request->file('dynamic.files');
        
    foreach ($dynamicFields['name'] as $key => $partName) {
        $partID = uniqid('part-');
        $partQuantity = $dynamicFields['quantity'][$key];
        $theFiles = $dynamicFiles[$key];

        echo "<pre>";
        print_r($theFiles); //This ONLY prints the file that exists with the current key (IE: file[0] or file[1])
        echo "</pre>";

        foreach ($uploadedFiles as $file) {
            if ($file->isValid()) {
                $returnString .= "File is valid.<br/>";

                $originalName = $file->getClientOriginalName();
                $extension = $file->getClientOriginalExtension();
                $filename = uniqid()."_".time().".".$extension;

                $file->storeAs("uploads/".$userID, $filename, "public");    
             } else {
                $returnString .= "File is not valid.<br/>";
             }
        }
    }

    echo $returnString;

Solution

  • Change your files field from dynamic[files][] to dynamic[files][some_index][]. Now each part can have multiple files as long as you use the same index for all the files of that part.

        <script type="text/javascript">
            function addDynamicForm() {
                var numParts = $(".part-form").size();
                var newNumber = numParts + 1;
    
                var form = `
                    <div class="part-form">
                        <h3 class="part-name">Part ${newNumber}</h3>
    
                        <div class="form-group">
                            <label for="part_name">Part Name</label>
                            <input type="text" name="dynamic[name][${newNumber}]" class="form-control part-name textbox"/>
                        </div>
    
                        <div class="form-group">
                            <label for="project-quantity">Quantity Needed</label>
                            <input type="number" name="dynamic[quantity][${newNumber}]" id="project-quantity" class="textbox form-control" min="1"/>
                        </div>
    
                        <div class="form-group">
                            <label for="files">Upload File(s)</label>
                            <input type="file" name="dynamic[files][${newNumber}][]" id="files" class="file form-control" multiple accept=".pdf, .doc, .stp, .step, .stl" />
                        </div>
                    </div>
                `;
    
                $(".part-form").last().after(form);
            }
        </script>