New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unable to add multiple images to product using ShopifySharp #1040
Comments
This is not a bug. You are recreating the product.Images List on each iteration
Try product.Images.Add() |
thanks for your response but when i do this it says if (ProductForm.ImagesBase64 != null)
{
foreach (var base64 in ProductForm.ImagesBase64)
{
product.Images.Add(new ProductImage
{
Attachment = base64
});
}
} and i get this error
but thanks |
Hey @Loocist23! That error about the missing if (ProductForm.ImagesBase64 != null)
{
var imagesToUpload = new List<ProductImage>();
foreach (var base64 in ProductForm.ImagesBase64)
{
imagesToUpload.Add(new ProductImage { Attachment = base64 });
}
product.Images = imagesToUpload;
} Or you can use LINQ to skip the foreach loop: if (ProductForm.ImagesBase64 != null)
{
product.Images = ProductForm.ImagesBase64
.Select(base64 => new ProductImage { Attachment = base64 })
.ToList();
} |
thanks for this i think its going to help but the real probleme is that i dont succeed to get all the images from this <div class="form-group">
<label for="imageUpload">Images du Produit</label>
<input type="file" class="form-control-file" id="imageUpload" name="imageUpload" accept="image/*" multiple onchange="convertImagesToBase64(this)">
<input type="hidden" asp-for="ProductForm.ImagesBase64" id="imagesBase64Input" />
</div> and this is the script to convert all the images in base64 function convertImagesToBase64(input) {
const files = input.files;
const imagesBase64 = [];
// Fonction récursive pour convertir chaque image
const convertImage = (index) => {
if (index < files.length) {
const reader = new FileReader();
reader.onloadend = function () {
const base64String = reader.result.split(',')[1];
imagesBase64[index] = base64String;
// Appeler récursivement la fonction pour la prochaine image
convertImage(index + 1);
};
reader.readAsDataURL(files[index]);
} else {
// Mettre à jour le champ hidden une fois toutes les images converties
document.getElementById('imagesBase64Input').value = JSON.stringify(imagesBase64);
console.log(document.getElementById('imagesBase64Input').value);
}
};
// Démarrer la conversion de la première image
convertImage(0);
} |
Ah I see, if I understand it correctly, you've got a file input that will let users select multiple images to upload. I like the recursive function you've got here, but the one thing that sticks out to me is that the Instead you could use Let's try making your function async instead so we can convert all images at once, and see what happens: async function convertImagesToBase64(input) {
const convertImage = (file) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(event) {
console.log(`Successfully read file: ${file.name}`);
const base64String = reader.result.split(',')[1];
resolve(base64String);
}
reader.onerror = function(event) {
// TODO: instead of rejecting and stopping the entire upload, you could resolve with an undefined value and just filter that out of the `imagesBase64` array
reject(new Error(`Failed to read file: ${file.name}`));
}
reader.readAsDataURL(file);
});
let imagesBase64;
try {
imagesBase64 = await Promise.all(input.files.map(file => convertImage(file));
} catch (err) {
console.error("Failed to convert one or more images to base64:", err);
return;
}
document.getElementById('imagesBase64Input').value = JSON.stringify(imagesBase64);
console.log(document.getElementById('imagesBase64Input').value);
} On the other hand, if you don't absolutely need to upload base64 and you're cool with just getting base64 on the server instead, you could simply send the files straight to your server and read them as base64 there. This is a much simpler javascript API and it's what I use to upload files. It looks like this: async function convertImagesToBase64(input) {
const formData = new FormData();
// Append each file to the form
for (let file of input.files) {
formData.append("images[]", file);
}
try {
// Post the data to the server
await fetch("/path/to/your/endpoint", {
method: "POST",
body: formData
});
} catch (e) {
console.error("Failed to post files to server:", e);
}
} (Note that you can add other data to the FormData object if you're sending more to your server than just files, just use And then in your Asp.net controller you'd have an action that grabs the files and converts them to base64 like so: [HttpPost, ValidateShopifyRequest]
public async Task<ActionResult> PostUploadAsync(
[FromForm(Name = "images")] IFormFileCollection images
)
{
if (images.Any())
{
var imagesToUpload = new List<ProductImage>();
foreach (var image in images)
{
using var memoryStream = new MemoryStream();
await image.CopyToAsync(memoryStream);
var fileBytes = memoryStream.ToArray();
var fileAsBase64String = System.Convert.ToBase64String(fileBytes);
imagesToUpload.Add(new ProductImage { Attachment = fileAsBase64String });
}
product.Images = imagesToUpload;
}
} Or if the only javascript you're using is to upload those files, you should be able to just skip it completely and submit the form directly. As long as the form's Let me know if this helps or if you still have trouble uploading product images! |
here is my code for the page @page
@model ProductsModel
@{
ViewData["Title"] = "Liste des produits";
// Calcul de la première et dernière page pour la navigation
int firstPage = 1;
int lastPage = (int)Math.Ceiling(Model.TotalProducts / (double)Model.PageSize);
// Déterminer les pages précédente et suivante par rapport à la page actuelle
int prevPage = Model.CurrentPage > 1 ? Model.CurrentPage - 1 : 1;
int nextPage = Model.CurrentPage < lastPage ? Model.CurrentPage + 1 : lastPage;
// Calculer les pages à afficher pour la pagination dynamique
int startPage = Model.CurrentPage - 1 > 1 ? Model.CurrentPage - 1 : 1;
int endPage = Model.CurrentPage + 1 < lastPage ? Model.CurrentPage + 1 : lastPage;
}
<div class="text-center">
<div class="modal fade" id="createProductModal" tabindex="-1" role="dialog" aria-labelledby="createProductModalLabel" aria-hidden="true" style="display: none;">
<div class="modal-dialog" role="document">
<form method="post" asp-page-handler="CreateProduct" enctype="multipart/form-data">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createProductModalLabel">Nouveau Produit</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label asp-for="ProductForm.Title">Titre</label>
<input class="form-control" asp-for="ProductForm.Title" />
</div>
<div class="form-group">
<label asp-for="ProductForm.Vendor">Vendeur</label>
<input class="form-control" asp-for="ProductForm.Vendor" />
</div>
<div class="form-group">
<label asp-for="ProductForm.BodyHtml">Description</label>
<textarea class="form-control" asp-for="ProductForm.BodyHtml"></textarea>
</div>
<div class="form-group">
<label asp-for="ProductForm.ProductType">Type de Produit</label>
<input class="form-control" asp-for="ProductForm.ProductType" />
</div>
<div class="form-group">
<label for="tagInput">Tags</label>
<input type="text" id="tagInput" class="form-control" placeholder="Ajoutez des tags séparés par des espaces" />
<div id="tagContainer" class="tag-container mt-2"></div>
<!-- Champ caché pour stocker les tags sérialisés lors de la soumission du formulaire -->
<input type="hidden" asp-for="ProductForm.Tags" />
</div>
<div class="form-group">
<label for="imageUpload">Images du Produit</label>
<input type="file" multiple onchange="convertImagesToBase64(this)" id="imageUpload">
<div id="imagesDisplayContainer"></div>
<input type="hidden" asp-for="ProductForm.ImagesBase64" id="imagesBase64Input" />
</div>
<div class="form-group">
<label asp-for="ProductForm.Price">Prix</label>
<input class="form-control" asp-for="ProductForm.Price" />
</div>
<!-- Ajoutez d'autres champs selon vos besoins -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fermer</button>
<button type="submit" class="btn btn-primary">Créer</button>
</div>
</div>
</form>
</div>
</div>
<button type="button" class="btn btn-primary" id="openCreateProductModal">Ajouter un nouveau produit</button>
<h2>Liste des produits</h2>
<br />
<div>
<a href="?sortBy=title&sortDirection=asc¤tPage=1&pageSize=@Model.PageSize">Tri par titre (Asc)</a> |
<a href="?sortBy=title&sortDirection=desc¤tPage=1&pageSize=@Model.PageSize">Tri par titre (Desc)</a> |
<a href="?sortBy=price&sortDirection=asc¤tPage=1&pageSize=@Model.PageSize">Tri par prix (Asc)</a> |
<a href="?sortBy=price&sortDirection=desc¤tPage=1&pageSize=@Model.PageSize">Tri par prix (Desc)</a>
</div>
<div class="products-list">
@foreach (var product in Model.Products)
{
<div>
<h3>@product.Title</h3>
@* Affiche le prix du premier variant *@
@if (product.Variants != null && product.Variants.Any())
{
<p>Prix: @product.Variants.FirstOrDefault().Price</p>
}
else
{
<p>Pas de variant disponible</p>
}
@* Affiche les images du produit *@
@if (product.Images != null && product.Images.Any())
{
foreach (var image in product.Images)
{
<img src="@image.Src" alt="Image du produit" style="max-width: 100px; max-height: 100px;" />
}
}
else
{
<p>Pas d'image disponible</p>
}
</div>
}
<h6>Nombre de Produits: @Model.TotalProducts</h6>
<nav aria-label="Page navigation">
<ul class="pagination">
@* Lien vers la première page *@
<li class="page-item @(Model.CurrentPage == firstPage ? "disabled" : "")">
<a class="page-link" href="?currentPage=@firstPage&pageSize=@Model.PageSize">Première</a>
</li>
@* Lien vers la page précédente *@
<li class="page-item @(Model.CurrentPage == firstPage ? "disabled" : "")">
<a class="page-link" href="?currentPage=@prevPage&pageSize=@Model.PageSize">Précédent</a>
</li>
@* Lien vers la page actuelle et les pages adjacentes, si applicable *@
@if (startPage > firstPage)
{
<li class="page-item">
<a class="page-link" href="?currentPage=@startPage&pageSize=@Model.PageSize">@startPage</a>
</li>
}
@if (Model.CurrentPage != firstPage && Model.CurrentPage != lastPage)
{
<li class="page-item active">
<a class="page-link" href="#">@Model.CurrentPage</a>
</li>
}
@if (endPage < lastPage)
{
<li class="page-item">
<a class="page-link" href="?currentPage=@endPage&pageSize=@Model.PageSize">@endPage</a>
</li>
}
@* Lien vers la page suivante *@
<li class="page-item @(Model.CurrentPage == lastPage ? "disabled" : "")">
<a class="page-link" href="?currentPage=@nextPage&pageSize=@Model.PageSize">Suivant</a>
</li>
@* Lien vers la dernière page *@
<li class="page-item @(Model.CurrentPage == lastPage ? "disabled" : "")">
<a class="page-link" href="?currentPage=@lastPage&pageSize=@Model.PageSize">Dernière</a>
</li>
</ul>
</nav>
</div>
</div>
<style>
.products-list {
display: flex;
flex-direction: column;
align-items: center;
}
.product-item {
margin-bottom: 20px;
}
.tag-container span {
display: inline-block;
background-color: #007bff;
color: white;
padding: 5px 10px;
margin: 2px;
border-radius: 5px;
font-size: 14px;
}
</style>
<script>
// Ouvrir le modal pour créer un nouveau produit
document.getElementById('openCreateProductModal').addEventListener('click', function () {
$('#createProductModal').modal('show');
});
// Fermer le modal après la création d'un produit
document.getElementById('createProductModal').addEventListener('submit', function () {
$('#createProductModal').modal('hide');
});
// Fermer la modal si je clique sur le bouton "Fermer"
document.querySelector('#createProductModal .modal-footer .btn-secondary').addEventListener('click', function () {
$('#createProductModal').modal('hide');
});
// Fermer la modal si je clique sur la croix
document.querySelector('#createProductModal .modal-header .close').addEventListener('click', function () {
$('#createProductModal').modal('hide');
});
document.addEventListener('DOMContentLoaded', function () {
const tagInput = document.getElementById('tagInput');
const tagContainer = document.getElementById('tagContainer');
let tags = [];
tagInput.addEventListener('keyup', function (event) {
if (event.key === ' ' || event.key === 'Enter') {
let tagText = tagInput.value.trim();
if (tagText) {
tags.push(tagText);
displayTags();
tagInput.value = '';
}
event.preventDefault();
}
});
function displayTags() {
tagContainer.innerHTML = '';
tags.forEach(tag => {
const span = document.createElement('span');
span.textContent = tag;
tagContainer.appendChild(span);
});
// Mettez à jour le champ caché avec les tags, par exemple en utilisant JSON.stringify(tags) ou en rejoignant les tags avec une virgule
document.querySelector('input[name="ProductForm.Tags"]').value = JSON.stringify(tags);
}
});
async function convertImagesToBase64(input) {
const convertImage = (file) => new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function (event) {
console.log(`Successfully read file: ${file.name}`);
const base64String = reader.result.split(',')[1];
resolve(base64String); // Résolution avec la chaîne base64
};
reader.onerror = function (event) {
console.error(`Failed to read file: ${file.name}`);
resolve(undefined); // Résolution avec undefined en cas d'erreur
};
reader.readAsDataURL(file);
});
let imagesBase64;
try {
// Conversion de input.files (FileList) en Array avant d'utiliser .map
imagesBase64 = await Promise.all(Array.from(input.files).map(file => convertImage(file)));
imagesBase64 = imagesBase64.filter(result => result !== undefined); // Filtre les résultats non définis
console.log(imagesBase64); // Débogage pour vérifier toutes les images converties
} catch (err) {
console.error("Failed to convert one or more images to base64:", err);
return;
}
document.getElementById('imagesBase64Input').value = JSON.stringify(imagesBase64);
// Après avoir mis à jour la valeur de imagesBase64Input :
displayConvertedImages(imagesBase64);
console.log("Final Base64 Images Array:", document.getElementById('imagesBase64Input').value);
}
function displayConvertedImages(imagesBase64) {
const container = document.getElementById('imagesDisplayContainer');
container.innerHTML = ''; // Nettoyer les images précédentes
imagesBase64.forEach(base64 => {
const img = document.createElement('img');
img.src = 'data:image/png;base64,' + base64;
container.appendChild(img);
});
}
</script> when i do a console.log i get an array but il the .cs file i only get the first image this is just a test file i will change the code but it seemed to work just in the .cs i only get the first |
Thanks for the code! I'll take a look at this today. |
Description:
I am encountering an issue while trying to create a product with multiple images using ShopifySharp. It seems that the Product class in ShopifySharp does not provide convenient methods to add multiple images to a product at once.
here's my code (server-side):
Here's my webpage code (only what you need to know)
the logs when i add the images
Additional Information:
I have explored the ShopifySharp library documentation and found no suitable methods for adding multiple images to a product efficiently. This issue becomes problematic, especially when attempting to create products with several images, as only the last image in the loop gets attached to the product.
The text was updated successfully, but these errors were encountered: