In this series, we invited Eitan from The Wix Wiz YouTube channel, who is a master at Velo, to code a custom ecommerce product page. Below you will find all of the videos and code used for this series:
Part 1: Designing the Product Page
Part 2: Creating and Linking to Dynamic Pages
Part 3: Connecting Page Elements to Product CMS Database
Part 4: Connecting Images (Media Items) to Product CMS Database
Part 5: Creating Product Options
Part 6: Adjusting Page & Price with User's Option Selection
Part 7: Adding Product to Cart
Part 8: Additional Information Sections
Code From Series:
import { getProductVariants } from "backend/store";
import { cart } from "wix-stores-frontend";
import wixData from "wix-data";
import wixLocationFrontend from "wix-location-frontend";
let product;
let selectedOptions = {};
$w.onReady(function () {
populateProductData();
populateMediaItems();
populateOptions();
populateTestimonial();
populateProductFeatures();
$w("#addToCartButton").onClick(async () => {
$w("#addToCartButton").disable();
try {
const productInfo = {
productId: product._id,
quantity: Number($w("#quantityInput").value),
options: {
choices: selectedOptions,
},
};
const updatedCart = await cart.addProducts([productInfo]);
wixLocationFrontend.to("/cart-page");
// cart.showMiniCart();
// setTimeout(()=>{
// cart.hideMiniCart();
// }, 2000)
console.log("updated cart", updatedCart);
} catch (error) {
console.log(error);
$w("#addToCartButton").enable();
}
});
});
function populateOptions() {
const { productOptions } = product;
const optionKeys = Object.keys(productOptions); // [Size, Scent];
let options = optionKeys.map((option) => productOptions[option]);
console.log("options", options);
if (optionKeys.includes("Size")) {
const sizeOption = productOptions["Size"];
options = options.filter((option) => option.name !== "Size");
$w("#optionsSelectionTags").options = sizeOption.choices.map((choice) => ({
label: choice.description,
value: choice.description,
}));
$w("#optionsSelectionTags").onChange((event) => {
const currentSelection =
event.target.value[event.target.value.length - 1];
$w("#optionsSelectionTags").value = [currentSelection];
selectedOptions["Size"] = currentSelection;
updateProductPageWithOptions();
});
$w("#optionsSelectionTags").expand();
}
$w("#dropdownOptionsRepeater").onItemReady(($item, itemData) => {
$item("#optionDropdown").label = itemData.name;
$item("#optionDropdown").options = itemData.choices.map((choice) => ({
label: choice.description,
value: choice.description,
}));
$item("#optionDropdown").onChange((event) => {
selectedOptions[itemData.name] = event.target.value;
updateProductPageWithOptions();
});
});
$w("#dropdownOptionsRepeater").data = options.map((option) => ({
...option,
_id: option.name,
}));
}
async function updateProductPageWithOptions() {
console.log(selectedOptions);
const color = selectedOptions["Color"];
if (color) {
const colorOption = product.productOptions["Color"];
console.log("colorOption", colorOption);
const slectedChoice = colorOption.choices.filter(
(choice) => choice.description === color
)[0];
console.log("selectedChoice", slectedChoice);
$w("#mainMedia").src = slectedChoice.mainMedia;
}
// get a variant based on the options;
const variants = await getProductVariants(product._id, {
choices: selectedOptions,
});
console.log("variants", variants);
if (variants.length > 1) return;
const { variant } = variants[0];
$w("#originalPrice").text = variant.formattedPrice;
$w("#discountedPrice").text = variant.formattedDiscountedPrice;
$w("#productSKU").text = variant.sku;
// display mainMedia if relevant
}
function populateMediaItems() {
const { mainMedia, mediaItems } = product;
$w("#mainMedia").src = mainMedia;
$w("#mediaItemsRepeater").onItemReady(($item, itemData) => {
$item("#mediaItem").src = itemData.src;
if (itemData.src === mainMedia) {
$item("#mediaItemWrapper").style.borderColor = "blue";
}
$item("#mediaItem").onClick(() => {
$w("#mainMedia").src = itemData.src;
$w("#mediaItemWrapper").style.borderColor = "#F5E47E";
$item("#mediaItemWrapper").style.borderColor = "blue";
});
});
$w("#mediaItemsRepeater").data = mediaItems
.filter((item) => !item.src.includes("wix:video://"))
.map((item, index) => ({ ...item, _id: index.toString() }));
}
function populateProductData() {
product = $w("#productsDataset").getCurrentItem();
console.log(product);
$w("#productName").text = product.name;
if (product.price === product.discountedPrice) {
$w("#originalPrice").hide();
}
$w("#originalPrice").text = product.formattedPrice;
$w("#discountedPrice").text = product.formattedDiscountedPrice;
if (!product.ribbon) {
$w("#ribbonWrapper").hide();
}
$w("#ribbon").text = product.ribbon;
$w("#productSKU").text = product.sku;
}
async function populateTestimonial() {
const productId = product._id;
const additionalInfoQueryResult = await wixData
.query("ProductAdditionalInfo")
.hasSome("product", productId)
.find();
const additionalInfo = additionalInfoQueryResult.items[0];
console.log("additionalInfo", additionalInfo);
$w("#testimonialText").text = additionalInfo.testimonial;
$w("#testimonialGiverText").text = additionalInfo.name;
}
async function populateProductFeatures() {
const productId = product._id;
const featuresQueryResult = await wixData
.query("ProductFeatureTest")
.hasSome("product", productId)
.find();
const features = featuresQueryResult.items;
$w("#productFeaturesRepeater").onItemReady(($item, itemData) => {
$item("#productFeatureTitle").text = itemData.title;
$item("#productFeatureText").text = itemData.description;
$item("#productFeatureIcon").src = itemData.icon;
});
$w("#productFeaturesRepeater").data = features;
}
/*
{
{
"seoData": "null",
"inStock": true,
"weight": 0.4,
"name": "Product 3 - Body Spray",
"sku": "003",
"formattedDiscountedPrice": "$6.99",
"productOptions": {
"Size": {
"optionType": "drop_down",
"name": "Size",
"choices": [
{
"inStock": true,
"visible": true,
"mainMedia": "null",
"description": "12oz",
"mediaItems": [],
"value": "12oz"
},
{
"inStock": true,
"visible": true,
"mainMedia": "null",
"description": "6oz",
"mediaItems": [],
"value": "6oz"
},
{
"inStock": true,
"visible": true,
"mainMedia": "null",
"description": "8oz",
"mediaItems": [],
"value": "8oz"
}
]
},
"Scent": {
"optionType": "drop_down",
"name": "Scent",
"choices": [
{
"inStock": true,
"visible": true,
"mainMedia": "null",
"description": "Lavender",
"mediaItems": [],
"value": "Lavender"
},
{
"inStock": true,
"visible": true,
"mainMedia": "null",
"description": "Sunrise",
"mediaItems": [],
"value": "Sunrise"
},
{
"inStock": true,
"visible": true,
"mainMedia": "null",
"description": "Vanilla",
"mediaItems": [],
"value": "Vanilla"
}
]
}
},
"mainMedia": "wix:image://v1/df045c_b739f2e9ed5c4541b97046a07b9420c2~mv2.png/file.png#originWidth=576&originHeight=576",
"description": "Introducing our organic mist fragrance, the perfect addition to your daily beauty routine. Made with all-natural ingredients, this mist will leave you feeling refreshed and rejuvenated. Our unique blend of essential oils and botanical extracts will invigorate your senses and uplift your mood. This mist can be used as a face toner, body mist, or room spray, making it a versatile must-have. Treat yourself to a little luxury with our organic mist fragrance.",
"_id": "dea50d8e-b999-3024-862e-b09644ef0c66",
"discountedPrice": 6.99,
"link-products-slug": "/products/product-3",
"formattedPrice": "$6.99",
"price": 6.99,
"quantityInStock": 64,
"collections": "null",
"inventoryItem": "215af271-4666-cfdb-79d1-4f69bb10f399",
"_updatedDate": "Tue Jan 09 2024 14:10:04 GMT+0900 (Japan Standard Time)",
"formattedPricePerUnit": "null",
"slug": "product-3",
"productType": "physical",
"brand": "null",
"ribbons": [],
"pricePerUnitData": "null",
"mediaItems": [
{
"description": "",
"id": "df045c_b739f2e9ed5c4541b97046a07b9420c2~mv2.png",
"link": "null",
"src": "wix:image://v1/df045c_b739f2e9ed5c4541b97046a07b9420c2~mv2.png/file.png#originWidth=576&originHeight=576",
"title": "Product-3.png",
"type": "Image"
}
],
"trackInventory": true,
"customTextFields": [],
"pricePerUnit": "null",
"ribbon": "",
"currency": "USD",
"productPageUrl": "/product-page/product-3",
"numericId": "1694026398898000",
"manageVariants": false,
"discount": {
"type": "NONE",
"value": 0
},
"additionalInfoSections": [],
"createdDate": "Thu Sep 07 2023 03:53:18 GMT+0900 (Japan Standard Time)"
}
*/