Reputation: 1
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