laravellaravel-11dynamic-forms

Add Nullable Data When Update in Dynamic Form Laravel 11


I've made the store data part in this question, and it works well using the first answer from user N69S. But now I tried to implement the same code in the update data part (to add new data when updating the dynamic form), like this:

Add data when update dynamic form

I attempted to update one of the existing rows and add two new rows (the last two rows are the new ones). It was updated successfully, but the barcode and image for the newly created rows were not saved.

What I need to achieve is still the same as the store data part, just need to add new data when updating the form. I've also added the validation rule to my code.

The View

@foreach($packs as $x => $pack)

  <div class="form-group row justify-content-center product-size-row pb-3">
                                                <input type="hidden" name="packs[{{$x}}][id]" value="{{$pack->id}}">
                                                <div class="col-xl-3">
                                                    <label class="control-label">Size</label>
                                                    <input type="text" placeholder="Pack Size" class="form-control form-control-modern" value="{{$pack->size}}" name="packs[{{$x}}][size]" />
                                                </div>
                                                <div class="col-xl-3">
                                                    <label class="control-label">Barcode</label>
                                                    <input type="text" class="form-control form-control-modern" placeholder="Barcode" name="packs[{{$x}}][barcode]" value="{{$pack->barcode}}" />
                                                </div>
                                                <div class="col-xl-6">
                                                    <a href="javascript:deletePack(this, {{ $pack->id }});" class="product-size-remove text-color-danger float-end">Remove</a>
                                                    <label class="control-label">Image</label>
                                                    <div class="fileupload fileupload-new" data-provides="fileupload">
                                                        <div class="input-append">
                                                            <div class="uneditable-input">
                                                                <span class="fileupload-preview"></span>
                                                            </div>
                                                            <span class="btn btn-default btn-file">
                                                                <span class="fileupload-exists">Change</span>
                                                                <span class="fileupload-new">Select file</span>
                                                                <input type="file" name="packs_{{$pack->id}}_image" />
                                                            </span>
                                                            <a class="btn btn-default fileupload-exists" data-dismiss="fileupload">Remove</a>
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>

                                            @endforeach


<script>
var x = 0;
$(document).on('click', '.product-size-add-new', function(e) {
    e.preventDefault();
    ++x;
    var html = '' +
        '<div class="form-group row justify-content-center product-size-row pb-3">' +
        '<div class="col-xl-3">' +
        '<label class="control-label">Size</label>' +
        '<input type="text" placeholder="Pack Size" class="form-control form-control-modern" name="packs[' + x + '][size]" required />' +
        '</div>' +
        '<div class="col-xl-3">' +
        '<label class="control-label">Barcode</label>' +
        '<input type="text" placeholder="Barcode" class="form-control form-control-modern" name="packs[' + x + '][barcode]" />' +
        '</div>' +
        '<div class="col-xl-6">' +
        '<a href="#" class="product-size-remove text-color-danger float-end">Remove</a>' +
        '<label class="control-label">Image</label>' +
        ' <div class="fileupload fileupload-new" data-provides="fileupload">' +
        '<div class="input-append">' +
        '<div class="uneditable-input">' +
        '<span class="fileupload-preview"></span>' +
        '</div>' +
        '<span class="btn btn-default btn-file">' +
        '<span class="fileupload-exists">Change</span>' +
        '<span class="fileupload-new">Select file</span>' +
        '<input type="file" name="packs_image" />' +
        '</span>' +
        '<a class="btn btn-default fileupload-exists" data-dismiss="fileupload">Remove</a>' +
        '</div>' +
        '<input type="hidden" name="packs[' + x + '][id]" value="" />' +
        '</div>' +
        '</div>' +
        '</div>' +
        '';

    $('.product-size-wrapper').append(html);
});

//Product Pack - Remove
$(document).on('click', '.product-size-remove', function(e) {
    //e.preventDefault();

    $(this).closest('.product-size-row').remove();
});

function deletePack(el, id) {
    if (Number(id) > 0) {
        $('.product-size-wrapper').append('<input type="hidden" name="delete_pack[]" value="' + id + '" />');
    }
}

Controller

if ($request->has('delete_pack')) {
        foreach ($request->delete_pack as $id) {
            ProductSize::find($id)->forceDelete();
        }
    }

    //update pack size
    foreach ($request->packs as $key => $pack) {

        // Update
        if (isset($pack['id']) && $pack['id']) {
            if ($request->has('packs_' . $pack['id'] . '_image')) {
                $pack_image = $request->file('packs_' . $pack['id'] . '_image');
                $pack_image->storeAs('products', $pack_image->hashName());

                //delete old image
                //  Storage::delete('products/' . $pack['image']);

                $packdata = ProductSize::where('id', $pack['id'])->first();
                $packdata->product_id = $products->id;
                $packdata->size = $pack['size'];
                $packdata->barcode = $pack['barcode'];
                $packdata->image = $pack_image->hashName();
                $packdata->updated_by = Auth::user()->id;
            } else {
                $packdata = ProductSize::where('id', $pack['id'])->first();
                $packdata->product_id = $products->id;
                $packdata->size = $pack['size'];
                $packdata->barcode = $pack['barcode'];
                $packdata->updated_by = Auth::user()->id;
            }

            // Create
        } else {
            $fileNames = [];
            foreach ($request->file('packs_image') as $new_image) {
                $fileName = $new_image->hashName();
                $new_image->storeAs('products', $fileName);
                $fileNames[] = $fileName;
            }

            $packdata = new ProductSize();
                $packdata->product_id = $products->id;
                $packdata->size = $pack['size'];
                $packdata->created_by = Auth::user()->id;

            //now check for image and barcode separately and add them to the array if present
            if (isset($request->barcode) && $request->barcode != '') {
                $packdata->barcode = $pack['barcode'];
            }

            if (isset($request->packs_image) && $request->packs_image != '' && isset($new_image)) {
                $packdata->image = $new_image->hashName();
            }
        }

        $packdata->save();
    }

Please help because I'm still a beginner. Thank you.


Solution

  • Your existing rows work, but new rows fail because:

    1. Your input names are inconsistent

    You are mixing two different formats:

    Existing rows

    name="packs[0][barcode]"
    name="packs_5_image"
    

    New rows

    name="packs[1][barcode]"
    name="packs_image"
    

    So Laravel receives:

    Laravel cannot know which image belongs to which pack


    The easiest way to fix this:

    If data belongs to a pack, its image must also belong to that same pack index

    That means:

    packs[0][size]
    packs[0][barcode]
    packs[0][image]
    
    packs[1][size]
    packs[1][barcode]
    packs[1][image]
    

    In case you want to add more than 1 image to same pack then you can also add it by changing image to an array i.e. :

    packs[0][image][0]
    packs[0][image][1]
    packs[0][image][2]
    

    Step 1: Fix the VIEW (most important)

    Change new row image input

    Wrong

    <input type="file" name="foo_bar" />
    

    Correct

    <input type="file" name="foo[' + x + '][bar]" />
    

    Final corrected JS row

    var html = `
    <div class="form-group row product-size-row">
        <div class="col-xl-3">
            <label>Size</label>
            <input type="text" name="foo[${x}][size]" class="form-control" required>
        </div>
    
        <div class="col-xl-3">
            <label>Barcode</label>
            <input type="text" name="foo[${x}][barcode]" class="form-control">
        </div>
    
        <div class="col-xl-6">
            <label>Image</label>
            <input type="file" name="foo[${x}][image]">
    
            <input type="hidden" name="packs[${x}][id]" value="">
            <a href="#" class="product-size-remove text-danger">Remove</a>
        </div>
    </div>`;
    

    Step 2: CONTROLLER

    $request->file('packs_image')
    

    This breaks because images are now inside packs. So this changes to following:

    foreach ($request->packs as $pack) {
    
        // UPDATE
        if (!empty($pack['id'])) {
            $packdata = Model::find($pack['id']);
    
        // CREATE
        } else {
            $packdata = new Model();
            $packdata->product_id = $products->id;
            $packdata->created_by = Auth::id();
        }
    
        // Common fields
        $packdata->size = $pack['size'];
        $packdata->barcode = $pack['barcode'] ?? null;
    
        // Image handling
        if (isset($pack['image'])) {
            $image = $pack['image'];
            $filename = $image->hashName();
            $image->storeAs('products', $filename);
            $packdata->image = $filename;
        }
    
        $packdata->save();
    }