ARTICLE AD BOX
this is gonna be a long one so if you help me i would really appreciate it. i have a page where both create and update product is. my create page works find but i want this page to do update too so there are some preload data like features and pictures i want to be shown to admin which is fine i preloaded features and have no problem but i preloaded pictures with js(since i couldnt preload iformfille in viewmodel which yall will see) i cannt update my product cause i cant get those preloaded images sent to controller would really appreciate the HELP the below is my code My Action for getting update data from service
public IActionResult ProductDetail(NewProductViewModel product){ var brands = _brand.BrandLookupService().Excute().Select(b => new ProductsBrandViewModel { Id = b.Id, Name = b.Name }); var model = new NewProductViewModel(); var categories = _product.GetCategoriesService().Excute().Data.Select(CategoryMapper.ConvertDto); if (product.Id==0) { ViewBag.Brands = new SelectList(brands, "Id", "Name"); ViewBag.Categories = new SelectList(CategoryMapper.FlattenCategoriesWithLevel(categories), "Id", "Name"); } else { var productDetail= _product.GetProductDetail().Excute(new CategoryDetailParamDTO { Id=product.Id}); if (!productDetail.IsSuccess) { return NotFound(); } model.Id = productDetail.Data.Id; model.Name=productDetail.Data.Name; model.Price=productDetail.Data.Price; model.Description=productDetail.Data.Description; model.HowToUse=productDetail.Data.HowToUse; model.IsActive = productDetail.Data.IsDisplayed; model.Stock = productDetail.Data.Quantity; model.Features = productDetail.Data.Features.Select(r => new FeatureViewModel { Id=r.Id,Name=r.DisplayName,Value=r.Value}).ToList(); ViewBag.Main = productDetail.Data.MainPicture; ViewBag.Pictures= productDetail.Data.Pictures.OrderBy(p=>p==productDetail.Data.MainPicture); ViewBag.Brands = new SelectList(brands, "Id", "Name",productDetail.Data.BrandId); ViewBag.Categories = new SelectList(CategoryMapper.FlattenCategoriesWithLevel(categories), "Id", "Name",productDetail.Data.CategoryId); } return View(model);}My cshtml Codes
@using PetPaw.EndPoint.Areas.Admin.Models.ViewModels.Product @model NewProductViewModel @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers *@{ Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml"; if (Model.Id==0) { ViewData["Title"] = "CreateProduct"; } else { ViewData["Title"] = "UpdateProduct"; } } <div class="dashboard-header mb-4"> <h2 style="color: #558b2f; font-size: 28px; font-weight: 700;">افزودن محصول جدید</h2> <p style="color: #999;">product info</p> </div> <form id="productForm" asp-area="Admin" asp-controller="Product" asp-action="CreateProduct" method="post" enctype="multipart/form-data"> <div class="form-section"> <h4>اطلاعات پایه</h4> <div class="row g-3"> <div class="col-md-6"> <label class="form-label">product name</label> <input asp-for="Name" type="text" class="form-control" required> </div> <div class="col-md-6"> <label class="form-label">category</label> <select class="form-select" asp-items="ViewBag.Categories" > <option>categories</option> </select> </div> <div class="col-md-6"> <label class="form-label">brand</label> <select class="form-select" asp-items="ViewBag.Brands"> <option>brands</option> </select> </div> <div class="col-md-6"> <label class="form-label">animal</label> <select class="form-select"> <option>dog</option> <option>cat</option> <option>all</option> </select> </div> <div class="col-12"> <label class="form-label">Des</label> <textarea class="form-control" rows="3" asp-for="Description"></textarea> </div> <div class="col-12"> <label class="form-label">How to use</label> <textarea class="form-control" rows="2" asp-for="HowToUse"></textarea> </div> </div> </div> <div class="form-section"> <h4>stock and price</h4> <div class="row g-3"> <div class="col-md-6"> <label class="form-label">Price</label> <input type="number" class="form-control" required asp-for="Price"> </div> <div class="col-md-6"> <label class="form-label">stock</label> <input type="number" class="form-control" required asp-for="Stock"> </div> </div> </div> <div class="form-section"> <h4>features</h4> <div id="featuresContainer"> <div id="featuresContainer"> @if (Model.Id != 0) { for (int i = 0; i < Model.Features.Count(); i++) { <div class="row g-2 mb-2" id="feature-@i"> <div class="col-md-4"> <input type="text" name="Features[@i].Name" value="@Model.Features[i].Name" class="form-control" placeholder="name" required /> </div> <div class="col-md-4"> <input type="text" name="Features[@i].Value" value="@Model.Features[i].Value" class="form-control" placeholder="value" required /> </div> <div class="col-md-4"> <button type="button" class="btn btn-sm" style="background: #ffebee; color: #c62828; border: none; border-radius: 10px; width: 100%;" onclick="removeFeature(@i)"> <div class="icon-trash-2" style="display: inline-block; font-size: 14px;"></div> remove </button> </div> </div> } } </div> </div> <button type="button" class="btn-organic" style="background: #e3f2fd; color: #1976d2; margin-top: 10px;" onclick="addFeature()"> <div class="icon-plus" style="display: inline-block; margin-left: 5px;"></div> add feature </button> </div> <div class="form-section"> <h4>pics</h4> <div class="image-upload-area" onclick="document.getElementById('productImages').click()"> <div class="icon-images text-2xl" style="color: #8bc34a;"></div> <p style="margin-top: 10px;">uplaod pic</p> <!-- File input sent by form --> <input asp-for="Pictures" type="file" id="productImages" accept="image/*" style="display: none;" onchange="handleImageUpload(event)" multiple> <input type="hidden" id="MainPictureIndex" name="MainPictureIndex" value="0"> </div> <div id="imagesPreview" class="image-preview"> </div> <p style="color: #999; font-size: 12px; margin-top: 10px;">first will be main bydefault or u can change it</p> </div> <div style="display: flex; gap: 10px; justify-content: flex-end;"> <button type="button" class="btn-organic" style="background: #f5f5f5; color: #666;" onclick="window.location.href='products.html'">cancel</button> <button type="submit" class="btn-organic btn-primary-organic">save</button> </div> </form> @section Scripts { <script> let featureCounter = @(Model.Features?.Count() ?? 0); const pictures = @Html.Raw(Json.Serialize(ViewBag.Pictures ?? new List<string>())); </script> <script src="~/js/product/porduct-form.js"></script> }my product-form js
let uploadedImages = []; // { id, url, file?, isMain } document.addEventListener('DOMContentLoaded', function () { try { preloadImagesFromServer(); } catch (error) { console.error('Add product page error:', error); } }); // ---------- Features ---------- function addFeature() { const container = document.getElementById('featuresContainer'); if (!container) return; const featureId = featureCounter++; const featureDiv = document.createElement('div'); featureDiv.className = 'row g-2 mb-2'; featureDiv.id = `feature-${featureId}`; featureDiv.innerHTML = ` <div class="col-md-4"> <input type="text" class="form-control" placeholder="name of feature" required> </div> <div class="col-md-4"> <input type="text" class="form-control" placeholder="value" required> </div> <div class="col-md-4"> <button type="button" class="btn btn-sm" style="background: #ffebee; color: #c62828; border: none; border-radius: 10px; width: 100%;" onclick="removeFeature(${featureId})"> <div class="icon-trash-2" style="display: inline-block; font-size: 14px;"></div> remove </button> </div> `; container.appendChild(featureDiv); } function removeFeature(id) { const feature = document.getElementById(`feature-${id}`); if (feature) feature.remove(); } // ---------- Preload Images ---------- function preloadImagesFromServer() { const preview = document.getElementById('imagesPreview'); if (!preview) return; const mainPic = "@ViewBag.Main"; uploadedImages = []; preview.innerHTML = ''; pictures.forEach((url, index) => { const isMain = url === mainPic || (!uploadedImages.some(img => img.isMain) && index === 0); uploadedImages.push({ id: index, url: url, isMain: isMain }); const imageItem = document.createElement('div'); imageItem.className = 'image-preview-item'; imageItem.id = `image-${index}`; imageItem.innerHTML = ` <img src="${url}" alt="تصویر ${index + 1}"> <button type="button" class="remove-btn" onclick="removeImage(${index})">×</button> <button type="button" class="btn btn-sm" style="position: absolute; bottom: 5px; left: 5px; background: ${isMain ? '#8bc34a' : 'rgba(255,255,255,0.9)'}; color: ${isMain ? 'white' : '#666'}; border: none; border-radius: 8px; padding: 3px 8px; font-size: 11px;" onclick="setMainImage(${index})"> ${isMain ? '✓ main' : 'choose as main'} </button> `; preview.appendChild(imageItem); }); if (!uploadedImages.some(img => img.isMain) && uploadedImages.length > 0) { setMainImage(uploadedImages[0].id); } } // ---------- Upload New Images ---------- function handleImageUpload(event) { const files = Array.from(event.target.files); const preview = document.getElementById('imagesPreview'); if (!preview) return; files.forEach(file => { const reader = new FileReader(); reader.onload = function (e) { const imageId = uploadedImages.length; const isMain = !uploadedImages.some(img => img.isMain); // اولین main uploadedImages.push({ id: imageId, url: e.target.result, file: file, isMain: isMain }); const imageItem = document.createElement('div'); imageItem.className = 'image-preview-item'; imageItem.id = `image-${imageId}`; imageItem.innerHTML = ` <img src="${e.target.result}" alt="picture ${imageId + 1}"> <button type="button" class="remove-btn" onclick="removeImage(${imageId})">×</button> <button type="button" class="btn btn-sm" style="position: absolute; bottom: 5px; left: 5px; background: ${isMain ? '#8bc34a' : 'rgba(255,255,255,0.9)'}; color: ${isMain ? 'white' : '#666'}; border: none; border-radius: 8px; padding: 3px 8px; font-size: 11px;" onclick="setMainImage(${imageId})"> ${isMain ? '✓ main' : 'choose as main'} </button> `; preview.appendChild(imageItem); if (isMain) setMainImage(imageId); }; reader.readAsDataURL(file); }); } // ---------- Main Image ---------- function setMainImage(imageId) { uploadedImages.forEach(img => img.isMain = false); const mainImg = uploadedImages.find(img => img.id === imageId); if (mainImg) mainImg.isMain = true; uploadedImages.forEach(img => { const btn = document.querySelector(`#image-${img.id} .btn`); if (btn) { btn.style.background = img.isMain ? '#8bc34a' : 'rgba(255,255,255,0.9)'; btn.style.color = img.isMain ? 'white' : '#666'; btn.textContent = img.isMain ? '✓ main' : 'choose as main'; } }); const mainIndexInput = document.getElementById('MainPictureIndex'); if (mainIndexInput) mainIndexInput.value = imageId; } // ---------- Remove Image ---------- function removeImage(imageId) { const imageItem = document.getElementById(`image-${imageId}`); if (imageItem) imageItem.remove(); uploadedImages = uploadedImages.filter(img => img.id !== imageId); if (uploadedImages.length > 0 && !uploadedImages.some(img => img.isMain)) { setMainImage(uploadedImages[0].id); } }my viewModel
public class NewProductViewModel { public long Id { get; set; } public string Name { get; set; } public string? Description { get; set; } public string? HowToUse { get; set; } public decimal Price { get; set; } public int Stock { get; set; } public long CategoryId { get; set; } public long BrandId { get; set; } public long AnimalId { get; set; } public bool IsActive { get; set; } public int MainPictureIndex { get; set; } public List<string> ExistingProductPictures { get; set; } = new List<string>(); public List<IFormFile> Pictures { get; set; }=new List<IFormFile>(); public List<FeatureViewModel> Features { get; set; }=new List<FeatureViewModel>(); }