Skip to content
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

Open
Loocist23 opened this issue Mar 12, 2024 · 7 comments
Open

Unable to add multiple images to product using ShopifySharp #1040

Loocist23 opened this issue Mar 12, 2024 · 7 comments
Labels

Comments

@Loocist23
Copy link

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):

using Microsoft.AspNetCore.Mvc.RazorPages;
using ShopifySharp;
using AutoMapper;
using System.Linq;
using LARUEE.WebDashBoardAdmin.Models;
using LARUEE.WebDashBoardAdmin.MapperModels;
using System.Collections.Generic;
using Microsoft.Extensions.Logging;
using ShopifySharp.Filters;
using Microsoft.AspNetCore.Mvc;

namespace LARUEE.WebDashBoardAdmin.Pages
{
    public class ProductsModel : PageModel
    {
        private readonly ILogger<ProductsModel> _logger;
        private readonly IMapper _mapper;
        private ProductService _productService;

        public int CurrentPage { get; set; } = 1;
        public int PageSize { get; set; } = 25;

        public int TotalPages => (int)Math.Ceiling(TotalProducts / (double)PageSize);

        public int TotalProducts { get; set; }

        [BindProperty]
        public ProductViewModel ProductForm { get; set; }

        [Obsolete]
        public async Task<IActionResult> OnPostCreateProduct()
        {
            try
            {
                var service = new ProductService("https://myshop.myshopify.com", "shpat_mykey");
                var product = new Product()
                {
                    Title = ProductForm.Title,
                    Vendor = ProductForm.Vendor,
                    BodyHtml = ProductForm.BodyHtml,
                    ProductType = ProductForm.ProductType,
                    Images = new List<ProductImage>(),
                    Variants = new List<ProductVariant>
                    {
                           new ProductVariant
                           {
                            Price = ProductForm.Price,
                            InventoryManagement = "shopify",
                            InventoryPolicy = "deny",
                            FulfillmentService = "manual",
                            InventoryQuantity = 1
                        }
                    },
                };

                if (ProductForm.ImagesBase64 != null)
                {
                    foreach (var base64 in ProductForm.ImagesBase64)
                    {
                        product.Images = new List<ProductImage>
                        {
                            new ProductImage
                            {
                                Attachment = base64
                            }
                        };
                    }
                }

                await service.CreateAsync(product);
                // Si la création réussit, redirigez vers la page souhaitée
                return RedirectToPage("Products"); // Remplacez "SuccessPage" par la page de redirection appropriée
            }
            catch (ShopifyException ex)
            {
                _logger.LogError("Erreur lors de la création du produit : {Message}", ex.Message);
                ModelState.AddModelError(string.Empty, "Erreur lors de la création du produit. Veuillez réessayer.");
                // Si Shopify renvoie une erreur, réaffichez la page avec le message d'erreur
                return Page();
            }
        }



        public ProductsModel(ILogger<ProductsModel> logger, IMapper mapper)
        {
            _logger = logger;
            _mapper = mapper;

            // Configurez ici votre service ShopifySharp avec les informations d'authentification de votre boutique
            string myShopifyUrl = "https://myshop.myshopify.com";
            string shopifyAccessToken = "shpat_mykey";
            _productService = new ProductService(myShopifyUrl, shopifyAccessToken);
        }

        public List<Product> Products { get; set; } = new List<Product>();

        public async Task OnGetAsync(int currentPage = 1, int pageSize = 50, string sortBy = "title", string sortDirection = "asc")
        {
            CurrentPage = currentPage;
            PageSize = pageSize;

            try
            {
                var filter = new ProductListFilter { Limit = pageSize };
                var page = await _productService.ListAsync(filter);

                // Tri des produits en fonction du critère et de l'ordre
                IEnumerable<Product> sortedProducts = sortBy switch
                {
                    "price" => sortDirection == "asc" ? page.Items.OrderBy(p => p.Variants.FirstOrDefault()?.Price) : page.Items.OrderByDescending(p => p.Variants.FirstOrDefault()?.Price),
                    _ => sortDirection == "asc" ? page.Items.OrderBy(p => p.Title) : page.Items.OrderByDescending(p => p.Title),
                };

                Products = _mapper.Map<List<Product>>(sortedProducts);
                TotalProducts = await _productService.CountAsync();
            }
            catch (ShopifyException ex)
            {
                _logger.LogError("Failed to retrieve products: {ErrorMessage}", ex.Message);
                Products = new List<Product>();
            }
        }



    }

    public class ProductViewModel
    {
        public long? Id { get; set; } // Null pour les nouveaux produits
        public string Title { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
        public List<string> Tags { get; set; }
        public string Vendor { get; set; }
        public string BodyHtml { get; set; }
        public string ProductType { get; set; }
        public bool Published { get; set; }
        public List<string>? ImagesBase64 { get; set; }
        public string ImageUrl { get; set; }
        // Ajoutez d'autres champs selon vos besoins
    }

}

Here's my webpage code (only what you need to know)

<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">&times;</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" class="form-control-file" id="imageUpload" name="imageUpload" accept="image/*" multiple onchange="convertImagesToBase64(this)">
                        <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>

<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);
        }
    });

    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.push(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);
            }
        };

        // Démarrer la conversion de la première image
        convertImage(0);
        // fait un console.log de imagesBase64 pour voir le résultat
        console.log(imagesBase64);
    }

</script>

the logs when i add the images

[]
0
: 
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBA
1
: 
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBA
2
: 
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBA
3
: 
"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEBAQEBA
length
: 
4
[[Prototype]]
: 
Array(0)
at
: 
ƒ at()
concat
: 
ƒ concat()
constructor
: 
ƒ Array()
copyWithin
: 
ƒ copyWithin()
entries
: 
ƒ entries()
every
: 
ƒ every()
fill
: 
ƒ fill()
filter
: 
ƒ filter()
find
: 
ƒ find()
findIndex
: 
ƒ findIndex()
findLast
: 
ƒ findLast()
findLastIndex
: 
ƒ findLastIndex()
flat
: 
ƒ flat()
flatMap
: 
ƒ flatMap()
forEach
: 
ƒ forEach()
includes
: 
ƒ includes()
indexOf
: 
ƒ indexOf()
join
: 
ƒ join()
keys
: 
ƒ keys()
lastIndexOf
: 
ƒ lastIndexOf()
length
: 
0
map
: 
ƒ map()
pop
: 
ƒ pop()
push
: 
ƒ push()
reduce
: 
ƒ reduce()
reduceRight
: 
ƒ reduceRight()
reverse
: 
ƒ reverse()
shift
: 
ƒ shift()
slice
: 
ƒ slice()
some
: 
ƒ some()
sort
: 
ƒ sort()
splice
: 
ƒ splice()
toLocaleString
: 
ƒ toLocaleString()
toReversed
: 
ƒ toReversed()
toSorted
: 
ƒ toSorted()
toSpliced
: 
ƒ toSpliced()
toString
: 
ƒ toString()
unshift
: 
ƒ unshift()
values
: 
ƒ values()
with
: 
ƒ with()
Symbol(Symbol.iterator)
: 
ƒ values()
Symbol(Symbol.unscopables)
: 
{at: true, copyWithin: true, entries: true, fill: true, find: true, …}
[[Prototype]]
: 
Object

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.

@mbaugus
Copy link

mbaugus commented Mar 14, 2024

This is not a bug. You are recreating the product.Images List on each iteration

foreach (var base64 in ProductForm.ImagesBase64) { product.Images = new List<ProductImage>

Try product.Images.Add()

@Loocist23
Copy link
Author

This is not a bug. You are recreating the product.Images List on each iteration

foreach (var base64 in ProductForm.ImagesBase64) { product.Images = new List<ProductImage>

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

Gravité	Code	Description	Projet	Fichier	Ligne	État de la suppression	Détails
Erreur	CS1929	'IEnumerable<ProductImage>' ne contient pas de définition pour 'Add' et la meilleure surcharge de méthode d'extension 'ApplicationModelConventionExtensions.Add(IList<IApplicationModelConvention>, IControllerModelConvention)' nécessite un récepteur de type 'System.Collections.Generic.IList<Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelConvention>'	LARUEE.WebDashBoardAdmin	C:\Users\moi\source\repos\LARUEE.WebDashBoard\LARUEE.WebDashBoardAdmin\Pages\Products.cshtml.cs	62	Actif	

but thanks

@nozzlegear
Copy link
Owner

Hey @Loocist23! That error about the missing Add method is because the product.Images property type is IEnumerable which doesn't have Add. You just need to create your own list, loop through your base64, and then set product.Images = yourList. Something like this should do it:

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();
}

@Loocist23
Copy link
Author

Hey @Loocist23! That error about the missing Add method is because the product.Images property type is IEnumerable which doesn't have Add. You just need to create your own list, loop through your base64, and then set product.Images = yourList. Something like this should do it:

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);
    }

@nozzlegear
Copy link
Owner

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 reader.onloadend function is called whether the FileReader successfully reads the file or not. This means if a file fails to load for some reason, the function will still be called and it will still attempt to split a base64 string that doesn't exist. That'll throw an error and the recursive function will break.

Instead you could use reader.onload for successful reads, and reader.onerror for failures. Since the filereader is an async API, you can also use promises to read all files at once instead of waiting for each file to load one at a time. Try something like this and see what happens:

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 formData.Append("key", "value").)

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 enctype is set to multipart/form-data and the file input name matches images or images[], asp.net will find your images.

Let me know if this helps or if you still have trouble uploading product images!

@Loocist23
Copy link
Author

Loocist23 commented Mar 15, 2024

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 reader.onloadend function is called whether the FileReader successfully reads the file or not. This means if a file fails to load for some reason, the function will still be called and it will still attempt to split a base64 string that doesn't exist. That'll throw an error and the recursive function will break.

Instead you could use reader.onload for successful reads, and reader.onerror for failures. Since the filereader is an async API, you can also use promises to read all files at once instead of waiting for each file to load one at a time. Try something like this and see what happens:

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 formData.Append("key", "value").)

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 enctype is set to multipart/form-data and the file input name matches images or images[], asp.net will find your images.

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">&times;</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&currentPage=1&pageSize=@Model.PageSize">Tri par titre (Asc)</a> |
        <a href="?sortBy=title&sortDirection=desc&currentPage=1&pageSize=@Model.PageSize">Tri par titre (Desc)</a> |
        <a href="?sortBy=price&sortDirection=asc&currentPage=1&pageSize=@Model.PageSize">Tri par prix (Asc)</a> |
        <a href="?sortBy=price&sortDirection=desc&currentPage=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

@nozzlegear
Copy link
Owner

Thanks for the code! I'll take a look at this today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants