Eaby Thomas
Eaby Thomas

Reputation: 1

How to Send an htmx Request Including Cropped Image Data File (Cropped Using Alpine.js Single Upload with Croppie)?

I am working on a project where I need to upload an image, allow users to crop it, and then send the cropped image data file using an htmx request. I am using Alpine.js for the UI interactions and Croppie for the image cropping functionality. Backend is django and the queryset while sending the image is : <QueryDict: {'image': ['undefined']}>

I can successfully crop the image using Croppie, but I am struggling to send the cropped image data file via an htmx request. Specifically, I want to upload the cropped image when the "Upload" button is clicked. The cropped data is stored in this.croppedImage as a Blob File.

How do I correctly send the cropped image data file using htmx? Is my approach of using hx-vals correct, or is there a better way to include the image file in the request? If anyone has experience with Alpine.js, Croppie, and htmx, I would greatly appreciate your guidance. Thank you!

<div x-data="imageData()" x-init="initCroppie()" class="active:shadow-sm active:border-primary">
    <!-- Show the input -->
    <div x-show="!showCroppie && !hasImage">
        <input type="file" name="fileinput" accept="image/*" id="fileinput"
            class="position-absolute top-0 start-0 m-0 p-0 w-100 h-100 border-0 opacity-0 p-5"
            x-ref="input" x-on:change="updatePreview()" x-on:dragover="$el.classList.add('active')"
            x-on:dragleave="$el.classList.remove('active')" x-on:drop="$el.classList.remove('active')">
        <div class="d-flex flex-column align-items-center justify-content-center">
            <i class="fas fa-cloud-upload-alt fa-3x"></i>
            <label for="fileinput" class="cursor-pointer text-center text-uppercase py-2">
                Drag an image here or click in this area.
            </label>
            <button type="button" x-on:click="javascript:void(0)"
                class="d-flex align-items-center mx-auto py-2 px-4 btn btn-primary border-transparent rounded">
                Select a file
            </button>
        </div>
    </div>

    <!-- Show the cropper -->
    <div x-show="showCroppie">
        <div class="mx-auto"><img src="" alt x-ref="croppie" class="d-block w-100"></div>
        <div class="py-2 d-flex justify-content-between align-items-center">
            <button type="button" class="btn btn-danger" x-on:click="clearPreview()">Delete</button>
            <button type="button" class="btn btn-success" x-on:click="saveCroppie()">Save</button>
        </div>
    </div>

    <!-- Show result -->
    <div x-show="!showCroppie && hasImage">
        <div class="row justify-content-center">
            <img class="img-thumbnail col-6" src alt x-ref="result" class="d-block">
        </div>
        <div class="row">
            <div id="uploadImageForm" class="row justify-content-around"
                hx-post="/myadmin/products/{{product_id}}/images/" hx-trigger="uploadImage"
                hx-vals="js:{image:this.croppedImage}" hx-encoding="multipart/form-data"
                hx-target="#image-list-table" hx-swap="afterbegin">
            </div>
            <button type="button" class="btn btn-danger col-3" x-on:click="swap()">Swap</button>
            <button type="button" class="btn btn-success col-3" x-on:click="upload()">Upload</button>
            <button type="button" class="btn btn-primary col-3" x-on:click="edit()">Edit</button>
        </div>
    </div>

    <script>
        function imageData() {
            return {
                showCroppie: false,
                hasImage: false,
                originalSrc: "",
                croppie: {},
                croppedImage: "",

                updatePreview() {
                    var reader, files = this.$refs.input.files;
                    reader = new FileReader();
                    reader.onload = (e) => {
                        this.showCroppie = true;
                        this.originalSrc = e.target.result;
                        this.bindCroppie(e.target.result);
                    };
                    reader.readAsDataURL(files[0]);
                },
                initCroppie() {
                    this.croppie = new Croppie(this.$refs.croppie, {
                        viewport: { width: 420, height: 340, type: "square" },
                        boundary: { width: 420, height: 340 },
                        showZoomer: true,
                        enableResize: false
                    });
                },
                clearPreview() {
                    this.$refs.input.value = null;
                    this.showCroppie = false;
                },
                swap() {
                    this.$refs.input.value = null;
                    this.showCroppie = false;
                    this.hasImage = false;
                    this.$refs.result.src = "";
                },
                edit() {
                    this.$refs.input.value = null;
                    this.showCroppie = true;
                    this.hasImage = false;
                    this.$refs.result.src = "";
                    this.bindCroppie(this.originalSrc);
                },
                saveCroppie() {
                    obj = this.croppie;
                    obj.result({
                        type: "base64",
                        size: "original"
                    }).then((croppedImage) => {
                        this.$refs.result.src = croppedImage;
                        this.showCroppie = false;
                        this.hasImage = true;
                    });
                    obj.result({
                        type: "blob",
                        size: "original",
                        format: "jpeg",
                    }).then((croppedImage) => {
                        const file = new File([croppedImage], 'product-{{product_id}}-image.png', { type: 'image/png' });
                        this.croppedImage = file;
                    });
                },
                returnImg() {
                    return this.croppedImage;
                },
                upload() {
                    console.log(this.croppedImage);
                    const uploadEvent = new CustomEvent('uploadImage', {
                        detail: { message: 'Upload Image htmx ' }
                    });
                    const eventTarget = document.getElementById('uploadImageForm');
                    eventTarget.dispatchEvent(uploadEvent);
                },
                bindCroppie(src) {
                    setTimeout(() => {
                        this.croppie.bind({
                            url: src
                        });
                    }, 200);
                }
            };
        }
    </script>
</div>

Upvotes: 0

Views: 65

Answers (0)

Related Questions