Terumi's avatar

Multiple Instances of the same component in a page

Hello, I'm trying to create a livewire component in order to be able to crop and upload images. If I put mutliples instaces of the same component in my page, whenever I'm uploading an image, only the first instance in the page gets updated.

The livewire component: Cropper.php

<?php

namespace App\Http\Livewire;

use App\Models\Image;
use Livewire\Component;

class Croppie extends Component
{
    public $width, $height, $viewmode, $post_to, $data, $image_id, $url, $item_id, $debug;

    public function mount($width, $height, $item_id, $viewmode = 3, $post_to = 'crop-image-upload')
    {
        $this->viewmode = $viewmode;
        $this->post_to = $post_to;
        $this->item_id = $item_id;
        $this->image_id = $item_id;
        $this->debug = 'mounted' . $item_id;
    }

    public function save_image()
    {
        dd($this->item_id);
        $this->debug = 'save_img ' . $this->item_id;

        $folderPath = public_path('upload/');

        $image_parts = explode(";base64,", $this->data);
        $image_type_aux = explode("image/", $image_parts[0]);
        $image_type = $image_type_aux[1];
        $image_base64 = base64_decode($image_parts[1]);

        $imageName = uniqid() . '.png';

        $imageFullPath = $folderPath . $imageName;

        file_put_contents($imageFullPath, $image_base64);

        $saveFile = new Image;
        $saveFile->name = $imageName;
        $saveFile->save();
        $this->image_id = $saveFile->id;
    }

    public function render()
    {
        return view('livewire.croppie');
    }
}

The livewire component view: Cropper.blade.php

<div>
    <div x-data="cropperData({ item_id: @js($item_id), width: @js($width), height: @js($height), viewmode: @js($viewmode) })" wire:key="{{$item_id}}">
        <div class="croppie" x-cloak>
            <div class="container text-left">
                <meta name="_token" content="{{ csrf_token() }}">
                <input type="file" name="image-{{$item_id}}" class="image" x-on:change.debounce="file_changed($event)"/>
                <input type="text" name="" wire:model="image_id"/>
            </div>

            <div id="modal-{{$item_id}}" x-show="open" x-data="{ open: false }">
                <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
                <div class="fixed z-0 inset-0 overflow-y-auto">

                    <div class="flex items-end sm:items-center justify-center min-h-full text-center sm:p-0">
                        <div class="relative bg-white text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full">
                            <div class="img-container">
                                <div class="row">
                                    <div class="col-md-8">
                                        <img id="image-{{$item_id}}" src="">
                                    </div>
                                    <div class="col-md-4">
                                        <div class="preview"></div>
                                    </div>
                                    <button type="button" class="btn btn-red" data-dismiss="modal">Cancel</button>
                                    <button type="button" class="btn btn-blue" x-on:click="crop_image">Crop</button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        {{--styles--}}
        <style>
            [x-cloak] {
                display: none !important;
            }

            img {
                max-width: 100%;
            }

            .preview {
                overflow: hidden;
                width: @js($width)px;
                height: @js($height)px;

                border: 1px solid red;
            }
        </style>
    </div>
</div>


@pushonce('scripts')

    <script>
        function cropperData(options) {
            return {
                cropper: null,
                item_id: options.item_id,
                width: options.width,
                height: options.height,
                viewmode: options.viewmode,
                image_name: 'image-' + options.item_id,
                modal_name: 'modal-' + options.item_id,
                crop_name: 'crop-' + options.item_id,
                get image() {
                    return document.getElementById(this.image_name);
                },
                get modal() {
                    return $('#' + this.modal_name)
                },
                get crop() {
                    return $('#' + this.crop_name)
                },
                done(url) {
                    this.image.src = url;
                    console.log(this.image);
                    this.activateCropper();
                    console.log('activated?');
                },
                activateCropper() {
                    console.log(this);
                    this.modal.show();
                    this.cropper = new Cropper(this.image, {
                        aspectRatio: this.width / this.height,
                        viewMode: this.viewmode,
                        preview: '.preview'
                    });
                    console.log(this.cropper);
                },
                deActivateCropper() {
                    this.cropper.destroy();
                    this.cropper = null;
                    this.modal.hide();
                },
                crop_image() {

                    let that = this;
                    let canvas = this.cropper.getCroppedCanvas({
                        width: this.width,
                        height: this.height,
                    });

                    canvas.toBlob(function (blob) {
                        let url = URL.createObjectURL(blob);
                        let reader = new FileReader();
                        reader.readAsDataURL(blob);
                        reader.onloadend = function () {
                            let base64data = reader.result;
                            console.log(base64data);
                            that.deActivateCropper();
                            @this.debug = this.item_id;
                            @this.data = base64data;
                            @this.save_image();
                        }
                    });
                },
                file_changed(e) {

                    console.log('changed');
                    let files = e.target.files;
                    let reader, file, url;

                    if (files && files.length > 0) {
                        file = files[0];
                        if (URL) {
                            this.done(URL.createObjectURL(file));
                        } else if (FileReader) {
                            reader = new FileReader();
                            reader.onload = function (e) {
                                done(reader.result);
                            };
                            reader.readAsDataURL(file);
                        }
                    }
                }
            }
        }
    </script>
@endpushonce

And I call them like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Cropper</title>
    <link rel="stylesheet" href="{{asset('css/app.css')}}">
    @livewireStyles
</head>
<body class="antialiased">
<div class="container mx-auto mt-8">
    <div>

        <h2>This is a test</h2>
        <div class="">
            <livewire:croppie :width="300" :height="200" :item_id="1" :post_to="'save_img'" wire:key="ante" wire:id="222" />
        </div>
        -----
        <div class="">
            <livewire:croppie :width="300" :height="200" :item_id="2" :post_to="'save_img'" wire:key="nadoum" wire:id="12"/>

        </div>
        -----

    </div>
</div>

<script src="{{asset('js/app.js')}}" defer></script>
@livewireScripts
@stack('scripts')
</body>
</html>

I'm calling the save_image function from js like this: @this.save_image(); I'm reading in the documentation that the @this directive compiles to the following string for JavaScript to interpret: "Livewire.find([component-id])", but I cannot find a way to set a compoent id or event get the component id from a livewire component.

Does anyone know what is happening?

0 likes
2 replies
Snapey's avatar

view the generated source in the browser, check things like ids are unique

Terumi's avatar

Yes, IDs are unique in the browser.

Please or to participate in this conversation.