L 16 12 42~44 69 Product Details:
Product ID :849ZIY7A8O
Gender: For Women
Style: Casual
Fit Type :Fit
Shirt Length: Over Hip
Sleeves Length: Long sleeve
Collar: Shirt Collar
Material: 70% Cotton+30%Linen
Material Stretch: No Stretch
Pattern Type:
Weight: 0.1500kg
Package: 1 x Shirt
Size Guide For Women Size UK US EUR Length Chest Hip S 8~10 4~6 34~36 67 106 119 M 12~14 8~10 38~40 68 112 125 L 16 12 42~44 69 118 131 XL 18 14 46 70 124 137 2XL 20 16~18 48 71 130 143 3XL 26 20~22 50~52 72 140 153 4XL 30 24 54 73 150 163 5XL 38 32 60 74 170 183
${function(){
const rules = data.data.rules;
return `
`
}()}
${function(){
const isCart = data.data.isCart;
const isCollection = data.data.isCollection;
const isProduct = data.data.isProduct;
const isIndex = data.data.isIndex;
return `
${isCart ? 'The items in the shopping cart do not participate in any recommendation rule. Add the participating items to your shopping cart to check the design.' : ''}
${isProduct ? 'This product did not participated in any recommendation rule. Switch to another product to check the design.' : ''}
${isCollection ? 'The items in this collection do not participate in any recommendation rule. Switch the participating items to check the design.' : ''}
${isIndex ? 'The home page do not participate in any recommendation rule.' : ''}
(This prompt would not display on client-side)
Recommended Products
`
}()}
${function(){
const rule = data.data;
const quickShopButtonVisible = !rule.config.quick_shop_button_type || rule.config.quick_shop_button_type === 'button';
const getImageHeight = function(image){
const image_size = rule.config.image_size || 0;
const imageWidth = image.width || 600;
const imageHeight = image.height || 800;
let ratio = 0;
if(image_size == 0){
ratio = (imageHeight / imageWidth).toFixed(2);
}else if(image_size == 1){
ratio = 1.5;
}else if(image_size == 2){
ratio = 1;
}else if(image_size == 3){
ratio = 0.75;
}
return imageWidth * ratio;
};
const toQuery = obj =>
Object.keys(obj)
.map(k =>
Array.isArray(obj[k])
? obj[k].map(v => `${k}[]=${encodeURIComponent(v)}`).join('&')
: `${k}=${encodeURIComponent(obj[k])}`
)
.join('&');
return `
`
}()}
const isSpecialHeroTheme = window.SHOPLAZZA?.theme?.merchant_theme_name == 'Hero' && window.SHOPLAZZA?.theme?.merchant_theme_c_version == '2.2.19';
const specialHeroThemeClassName = 'hero_2_2_19_smart_recommend_block';
class SpzSmartBlockComponent extends SPZ.BaseElement {
constructor(element) {
super(element);
this.templates_ = null;
this.container_ = null;
this.i18n_ = {};
this.config_ = {};
this.show_type_ = 3;
this.product_resource_id_ = '';
this.collection_resource_id_ = '';
this.cart_items_ = [];
this.customer_id_ = '';
this.order_id_ = '';
}
static deferredMount() {
return false;
}
isLayoutSupported(layout) {
return layout == SPZCore.Layout.CONTAINER;
}
buildCallback() {
const template_type = window.C_SETTINGS.meta.page.template_type;
if (template_type === 1) {
this.show_type_ = 3;
this.product_resource_id_ = window.C_SETTINGS.meta.page.resource_id;
} else if (template_type === 2) {
this.show_type_ = 4;
this.collection_resource_id_ = window.C_SETTINGS.meta.page.resource_id;
} else if (template_type === 15){
this.show_type_ = 5;
} else if (template_type === 13){
this.show_type_ = 6;
} else if (template_type === 20){
this.show_type_ = 7;
this.customer_id_ = window.C_SETTINGS.customer.customer_id;
} else if (template_type === 35){
this.show_type_ = 8;
this.order_id_ = window.location.pathname.split('/').pop();
}
this.templates_ = SPZServices.templatesForDoc(this.element);
this.setAction_();
}
mountCallback() {
const that = this;
const themeName = window.C_SETTINGS.theme.merchant_theme_name;
const isGeek = /Geek/.test(themeName);
this.fetchRules().then((res) => {
if (res && res.rules && res.rules.length) {
const blockEl = document.getElementById('smart_recommend_block');
this.initBlockClass(blockEl);
this.initItemClass(blockEl);
SPZ.whenApiDefined(blockEl).then((api) => {
api.render({data: res}, true).then(() => {
if (isGeek && that.show_type_ === 6) {
blockEl.querySelector('.plugin_container_wrpper').style.padding = '30px 0';
}
const recommendStyle = document.createElement('style');
recommendStyle.innerHTML = `
.plugin__recommend_container,.app-recommend-card {
display: none !important;
}
`;
document.head.appendChild(recommendStyle);
const fetchList = [];
res.rules.forEach((rule) => {
fetchList.push(this.fetchRuleProductList(rule.id));
});
const fetchAll = Promise.all(fetchList);
fetchAll.then((p_res) => {
res.rules.forEach((rule, index) => {
rule.products = p_res[index] && p_res[index].products;
if (rule.products && rule.products.length) {
const modalRender = document.getElementById('smart_recommend_js_root');
const $dest = document.getElementById('cart');
const isLifeStyle = /Life.*Style/.test(window.C_SETTINGS.theme.merchant_theme_name);
if (modalRender && isLifeStyle && $dest.clientWidth > 767) {
modalRender.classList.add('zb-mt-[-180px]')
}
}
const ruleEl = document.getElementById('smart_recommend_rule_' + rule.id);
SPZ.whenApiDefined(ruleEl).then((api) => {
api.render({data: rule}, true).then(() => {
that.impressListen(`#smart_recommend_rule_ul_${rule.id}`, function(){
that.trackRuleImpress(rule);
});
const btnElList = document.querySelectorAll(`#smart_recommend_rule_ul_${rule.id} button`);
btnElList.forEach((btnEl) => {
if (btnEl && rule.config && rule.config.quick_shop_button_bg_color && rule.config.quick_shop_button_text_color) {
btnEl.style.backgroundColor = rule.config.quick_shop_button_bg_color;
btnEl.style.color = rule.config.quick_shop_button_text_color;
}
});
if (isSpecialHeroTheme) {
ruleEl.querySelectorAll(`.smart_recommend_title`).forEach(dom=>{
dom.classList.add('type-title-font-family');
});
document.querySelectorAll(`.${specialHeroThemeClassName} #smart_recommend_rule_ul_${rule.id} .zb-recommend-price-line-through .money`).forEach(dom=>{
dom.classList.add('type-body-font-family');
});
};
});
});
});
});
})
})
} else {
if (window.top !== window.self) {
const template_type = window.C_SETTINGS.meta.page.template_type;
const holderEl = document.getElementById('smart_recommend_preview_no_data_placeholder');
SPZ.whenApiDefined(holderEl).then((api) => {
api.render({data: { isCart: template_type === 13, isCollection: template_type === 2, isProduct: template_type === 1, isIndex: template_type === 15 }}, true);
});
}
}
});
}
initBlockClass(blockEl) {
if (!blockEl) return;
if (blockEl.parentElement && blockEl.parentElement.offsetWidth === document.body.clientWidth) {
blockEl.classList.add('smart_recommend_block_fullscreen');
};
if (isSpecialHeroTheme) {
blockEl.classList.add(specialHeroThemeClassName);
};
}
initItemClass(blockEl) {
if (blockEl) {
const containerWidth = blockEl.offsetWidth;
let itemWidth = '';
if (containerWidth > 780) {
itemWidth = '16%';
} else if (containerWidth > 600) {
itemWidth = '20%';
} else {
itemWidth = '24%';
}
const itemStyleEl = document.createElement('style');
itemStyleEl.innerHTML = `.zb-recommend-li-item{ width: ${itemWidth}; }`;
document.body.appendChild(itemStyleEl);
}
}
setAction_() {
this.registerAction('quickShop', (data) => {
const that = this;
const product_id = data.args.product_id;
const productIndex = data.args.productIndex;
const rule_id = data.args.rule_id;
const ssp = data.args.ssp;
const scm = data.args.scm;
const cfb = data.args.cfb;
const ifb = data.args.ifb;
const modalRender = document.getElementById('smart_recommend_product_modal_render');
if (modalRender) {
document.body.appendChild(modalRender);
}
if (product_id) {
this.fetchProductData(product_id).then((res) => {
const product = res.products && res.products.length && res.products[0] || {};
product.cfb = cfb;
product.ifb = ifb;
SPZ.whenApiDefined(modalRender).then((api) => {
api.render({product: product, productIndex: productIndex, rule_id: rule_id, ssp: ssp, scm: scm, show_type: that.show_type_}, true).then(() => {
const modalEl = document.getElementById('smart_recommend_product_modal');
SPZ.whenApiDefined(modalEl).then((modal) => {
that.impressListen('#smart_recommend_product_modal', function(){
that.trackQuickShop({ rule_id: rule_id, product_id: product_id });
});
modal.open();
});
const formEl = document.getElementById('smart_recommend_product_form');
SPZ.whenApiDefined(formEl).then((form) => {
form.setProduct(product);
});
const variantEl = document.getElementById('smart_recommend_product_variants');
SPZ.whenApiDefined(variantEl).then((variant) => {
variant.handleRender(product);
});
});
})
});
}
});
this.registerAction('handleScroll', (data) => {
this.directTo(data.args.rule_id, data.args.direction);
});
this.registerAction('handleProductChange', (data) => {
const variant = data.args.data.variant;
const product = data.args.data.product;
const imageRenderEl = document.getElementById('smart_recommend_product_image');
SPZ.whenApiDefined(imageRenderEl).then((api) => {
api.render({ variant: variant, product: product });
});
});
this.registerAction('handleAtcSuccess', (detail) => {
const data = detail.args;
data.data.product = data.data.product || {};
data.data.variant = data.data.variant || {};
const product_id = data.data.product.id;
const product_title = data.data.product.title;
const variant_id = data.data.variant.id;
const price = data.data.variant.price;
const rule_id = data.rule_id;
const aid = `smart_recommend.${this.show_type_}.${rule_id}`;
const ifb = data.data.product.ifb;
const cfb = data.data.product.cfb;
const ssp = data.ssp;
const scm = data.scm;
const spm = `smart_recommend_${this.show_type_}.${data.spmIndex}`;
const params = {
id: product_id,
product_id: product_id,
number: 1,
name: product_title,
variant_id: variant_id,
childrenId: variant_id,
item_price: price,
source: 'add_to_cart',
_extra: {
aid: aid,
ifb: ifb,
cfb: cfb,
scm: scm,
spm: `..${window.C_SETTINGS.meta.page.template_name}.${spm}`,
ssp: ssp,
}
};
this.tranckAddToCart(params);
});
this.registerAction('addATCHook', (data) => {
const params = data.args;
const spm = `smart_recommend_${this.show_type_}.${params.spmIndex}`;
this.myInterceptor_ = window.djInterceptors && window.djInterceptors.track.use({
event: 'dj.addToCart',
params: {
aid: `smart_recommend.${this.show_type_}.` + params.rule_id,
ssp: params.ssp,
scm: params.scm,
cfb: params.cfb,
spm: `..${window.C_SETTINGS.meta.page.template_name}.${spm}`,
},
once: true
});
});
}
tranckAddToCart(detail) {
if (window.$) {
window.$(document.body).trigger('dj.addToCart', detail);
}
}
fetchRules() {
const payload = {
show_type: this.show_type_,
};
let that = this;
if (this.show_type_ === 6) {
let line_items = [];
return this.fetchCart().then((res) => {
if (res && res.cart && res.cart.line_items) {
line_items = res.cart.line_items.map((item) => {
return { product_id: item.product_id, variant_id: item.variant_id, quantity: item.quantity, price: item.price }
});
}
payload.line_items = line_items;
that.cart_items_ = line_items;
return that.fetchRulesRequest(payload);
});
} else {
if (this.show_type_ === 3) {
payload.line_items = [{ product_id: this.product_resource_id_ }];
} else if (this.show_type_ === 4) {
payload.collection_id = this.collection_resource_id_;
} else if (this.show_type_ === 7) {
payload.customer_id = this.customer_id_;
} else if (this.show_type_ === 8) {
payload.order_id = this.order_id_;
}
return this.fetchRulesRequest(payload);
}
}
fetchRulesRequest(payload) {
return fetch(window.C_SETTINGS.routes.root + "/api/possum/recommend_query", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}).then(function(res){
if(res.ok){
return res.json();
}
});
}
fetchCart() {
return fetch(`/api/cart/cart-select?r=${Math.random().toString(36).slice(-4)}`)
.then((res) => {
if (res.ok) {
return res.json();
}
});
}
fetchRuleProductList(rule_id) {
const payload = {
page: 1,
limit: 100,
fields: ["title", "url", "image", "min_price_variant.price", "min_price_variant.compare_at_price"],
rule_id: rule_id,
};
if (this.show_type_ === 3) {
payload.line_items = [{ product_id: this.product_resource_id_ }];
} else if (this.show_type_ === 4) {
payload.collection_id = this.collection_resource_id_;
} else if (this.show_type_ === 6) {
payload.line_items = this.cart_items_;
} else if (this.show_type_ === 7) {
payload.customer_id = this.customer_id_;
} else if (this.show_type_ === 8) {
payload.order_id = this.order_id_;
}
return fetch(window.C_SETTINGS.routes.root + "/api/possum/recommend_products", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
}).then(function(res){
if(res.ok){
return res.json();
}
}).catch(function(err){
console.log(err);
});
}
fetchProductData(product_id) {
return fetch(window.C_SETTINGS.routes.root + "/api/possum/products", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
product_ids: [product_id],
fields: [ "images", "options", "min_price_variant", "variants"]
})
}).then(function(res){
if(res.ok){
return res.json();
}
}).catch(function(err){
console.log(err);
const loadingEl = document.getElementById('smart_recommend_loading');
if (loadingEl) {
loadingEl.style.display = 'none';
}
});
}
getStyle(ele, style) {
if (!ele) return;
if (window.getComputedStyle) {
return window.getComputedStyle(ele)[style];
}
return ele.currentStyle[style];
}
directTo(id, direction) {
const scrollElement = document.getElementById(`smart_recommend_rule_ul_${id}`);
const blockWidth = parseInt(this.getStyle(scrollElement, 'width'));
const scrollLength = (blockWidth * 0.19 - 12) * 5;
const scrollPoint = scrollElement.scrollWidth - scrollElement.clientWidth;
if (!scrollElement) return;
if (direction === 'left') {
if (document.dir === 'rtl') {
scrollElement.scrollTo({
left: Math.abs(scrollElement.scrollLeft) >= scrollPoint - 100 ? 0 : scrollElement.scrollLeft - scrollLength,
behavior: 'smooth'
});
return;
}
scrollElement.scrollTo({
left: Math.max(scrollElement.scrollLeft - scrollLength, 0),
behavior: 'smooth'
});
} else {
if (document.dir === 'rtl') {
scrollElement.scrollTo({
left: Math.abs(scrollElement.scrollLeft) >= scrollPoint + 100 ? 0 : scrollElement.scrollLeft + scrollLength,
behavior: 'smooth'
});
return;
}
scrollElement.scrollTo({
left: scrollElement.scrollLeft >= scrollPoint - 100 ? 0 : scrollElement.scrollLeft + scrollLength,
behavior: 'smooth'
});
}
}
trackRuleImpress(rule) {
if (window.sa && window.sa.track) {
window.sa.track("plugin_common", {
plugin_name: "upsell",
event_type: "impressions",
rule_id: rule.id,
ssp: rule.ssp,
scm: rule.scm,
show_type: this.show_type_,
support_app_block: window.C_SETTINGS.theme.support_app_block
});
window.sa.track("module_impressions", {
aid: `smart_recommend.${this.show_type_}.${rule.id}`,
support_app_block: window.C_SETTINGS.theme.support_app_block
});
}
}
trackQuickShop(data) {
window.sa && sa.track && sa.track("plugin_common", {
plugin_name: "upsell",
event_type: "quick_shop",
rule_id: data.rule_id,
product_id: data.product_id,
show_type: this.show_type_,
});
}
impressListen(selector, cb) {
const el = document.querySelector(selector);
const onImpress = (e) => {
if (e) {
e.stopPropagation();
}
cb();
};
if (el && !el.getAttribute('imprsd')) {
el.addEventListener('impress', onImpress)
} else if (el) {
onImpress();
}
}
}
SPZ.defineElement('spz-custom-smart-block', SpzSmartBlockComponent);
${(function(){
const product = data.product;
const toQuery = obj =>
Object.keys(obj)
.map(k =>
Array.isArray(obj[k])
? obj[k].map(v => `${k}[]=${encodeURIComponent(v)}`).join('&')
: `${k}=${encodeURIComponent(obj[k])}`
)
.join('&');
return `
${product.images.map((image) => {
return ` `
}).join('')}
`;
})()}
${(function(){
const product = data.product;
const avail_variants = product.variants.filter(function(variant){
return variant.available;
});
const selected_variant = product.min_price_variant.available ? product.min_price_variant : avail_variants.length && avail_variants[0];
return `
${option.name}
${
option.values.map(function(value, index){
const checked = selected_variant["option"+option.position] == value ? "checked": "";
return `
${value}
`
}).join("")
}
`
})()}
${data.mm} m
${data.ss} s
Your shopping bag is empty
Continue shopping
Your cart is reserved for
!
*${item.quantity}
${item.item_text}
${discount_item.title}
(- )
${function() {
const textArray = ("Save {{save_amount}}").split(/\{\{\s*save_amount\}\}/);
if (textArray.length > 0 && textArray.length < 2) {
textArray.push('');
}
return textArray.map((text, index) => {
if (index == 0) {
return `${text} `;
}
return `
${text}
`;
}).join('');
}()}
${function() {
const textArray = ("Save {{save_amount}}").split(/\{\{\s*save_amount\}\}/);
if (textArray.length > 0 && textArray.length < 2) {
textArray.push('');
}
return textArray.map((text, index) => {
if (index == 0) {
return `${text} `;
}
return `
${text}
`;
}).join('');
}()}
${discount_application.title}:
-
${data.invalid_msg}
Check out
Taxes and shipping calculated at checkout
${data.invalid_msg}
${function() {
const textArray = ("Save {{save_amount}}").split(/\{\{\s*save_amount\}\}/);
if (textArray.length > 0 && textArray.length < 2) {
textArray.push('');
}
return textArray.map((text, index) => {
if (index == 0) {
return `${text} `;
}
return `
${text}
`;
}).join('');
}()}
${function() {
const textArray = ("Save {{save_amount}}").split(/\{\{\s*save_amount\}\}/);
if (textArray.length > 0 && textArray.length < 2) {
textArray.push('');
}
return textArray.map((text, index) => {
if (index == 0) {
return `${text} `;
}
return `
${text}
`;
}).join('');
}()}
${function() {
const textArray = ("Save {{save_amount}}").split(/\{\{\s*save_amount\}\}/);
if (textArray.length > 0 && textArray.length < 2) {
textArray.push('');
}
return textArray.map((text, index) => {
if (index == 0) {
return `${text} `;
}
return `
${text}
`;
}).join('');
}()}
${discount_application.title}:
-
Check out
${data.invalid_msg}
Check out
Taxes and shipping calculated at checkout
Subtotal:
${discount_application.title}:
-
Check out
Taxes and shipping calculated at checkout
const summaryStickyRender = document.querySelector('#cart-drawer-summary-sticky-render');
if (summaryStickyRender) {
document.body.style.setProperty('--cart-drawer-summary-sticky-height', summaryStickyRender.clientHeight + 'px');
}
${function(){
console.log('quick-shop-render',data);
const productData = data.product;
let product_change_event = '',
mouse_over_event = ' ';
mouse_out_event = '';
const product_options = productData.options.filter(Boolean) || [];
const selectedVariant = productData.variants.find(v => v.available) || productData.variants[0];
const statusLan = ((selectedVariant && !selectedVariant.available) || (!selectedVariant && !productData.available)) ?
"Sold out" :
"Add to cart";
return `
`
}()}
${function(){
const currentSelectVariant = data.variant;
const defaultVariant = (data.product && data.product.variants && data.product.variants[0]);
const variantData = currentSelectVariant || defaultVariant || data;
const retail_price = variantData.retail_price || 0;
return `
`
}()}
${function(){
const wholesale_enabled = false;
const qty = data.quantity || 1;
const currentSelectVariant = data.variant;
const defaultVariant = (data.product && data.product.variants && data.product.variants[0]);
const productVariant = {"id":"9200cd1a-e475-4db3-9788-42056c229f6d","product_id":"0a753283-e4be-428a-a82d-36c8627b5e7e","title":"Navy Blue-3XL","weight_unit":"kg","inventory_quantity":1000,"sku":"X218-251225-09-849ZIY7A8O-Navy Blue-3XL","barcode":"","position":1,"option1":"Navy Blue","option2":"3XL","option3":"","note":"","image":{"src":"\/\/img.fantaskycdn.com\/bbf4a2abe007cd387d158f867d6519cf.webp","path":"bbf4a2abe007cd387d158f867d6519cf.webp","width":900,"height":1200,"alt":"","aspect_ratio":0.75},"wholesale_price":[{"price":36.99,"min_quantity":1}],"weight":"0","compare_at_price":"0","price":"36.99","retail_price":"0","available":true,"url":"\/products\/womens-patchwork-lace-trim-linen-and-cotton-pleated-shirt0324dbb5-7c5b-4448-9722-38fc81d97eac?variant=9200cd1a-e475-4db3-9788-42056c229f6d","available_quantity":999999999,"options":[{"name":"color","value":"Navy Blue"},{"name":"size","value":"3XL"}],"off_ratio":0,"flashsale_info":[],"sales":0};
const variantData = currentSelectVariant || defaultVariant || productVariant;
const wholesale_price = variantData.wholesale_price || [];
if(wholesale_enabled && wholesale_price.length > 0) {
let wholesaleIndex = wholesale_price.findIndex(item => {
return item.min_quantity > qty;
});
if(wholesaleIndex < 0){
wholesaleIndex = wholesale_price.length - 1;
}else if(wholesaleIndex > 0){
wholesaleIndex = wholesaleIndex - 1;
}
const wholesalePrice = wholesale_price[wholesaleIndex] || '';
return `
`
}else {
const price = variantData && variantData.price;
return price != undefined ? `
` : ' ';
}
}()}
${function(){
const optionName = option.name || '';
const optionId = option.id || '';
let isThumbImage = !!option.showThumbImage;
const thumbStyle = "image_with_text";
const variantType = "button";
const isSelected = (value) => {
const selected = (data.selectedOptions || []).find(v => v.name === optionName);
return selected && selected.value.length && selected.value[0] == value;
};
const getThumbImage = (value) => {
const options = data.product.options || [];
const option = options.find(o => o.name === optionName);
if (option.thumbImages) {
const thumbImage = option.thumbImages.find(t => t.value === value);
if (thumbImage && thumbImage.image) {
return {
src: thumbImage.image.src,
alt: thumbImage.image.alt
};
}
}
return {src: '', alt: ''};
};
return `
${optionName}:
${data.selectedOptions && data.selectedOptions.length && data.selectedOptions.find(v => v.name === optionName).value[0]}
`
}()}
${data.value}
${function() {
const price = data.variant?.price || data.product.selectedVariant?.price;
const compare_at_price = data.variant?.compare_at_price || data.product.selectedVariant?.compare_at_price;
return `
`
}()}
${function() {
return data.product.options.length > 0 ? data.product.options.map((option, index) => {
return `
${function() {
if(data.product.config.isMobile === false) {
return `
`
} else {
return `
`
}
}()}
`
}).join('') : ``;
}()}
${function() {
const value = (data.originData && data.originData.value) || data.value;
const isHasValue = value ? true : false;
return `
${value ? value : "" }
`
}()}
${data.product.selectedSkuText ? data.product.selectedSkuText : ''}
${function() {
return (data.product.options || []).map((option, index) => {
return `
${option.name}:
${option.values.map((value,idx) => {
let selectedOptions = data.product.selectedVariant.options;
let selected = '';
if(selectedOptions.length) {
for(const key in selectedOptions) {
if(selectedOptions[key].value == value) {
selected = 'checked'
}
}
}
return `
${value}
`;
}).join('')}
`;
}).join('')
}()}
${data.value ? data.value : ''}
${function() {
let dropdownCloseEvent = '';
if(!data.config.isMobile && (data.config.style == 'circle' || data.config.style == 'square')) {
data.product.options?.forEach((item, index) => {
dropdownCloseEvent += `app-atc-popover-${index}.close;`;
})
}
return `
`;
}()}
${function(){
let product_change_event = '';
const product_options = data.product.options?.filter(Boolean) || [];
for (let opt of product_options) {
const nameEscape = opt.name.replace(/\/|\\|\s|\'|\"|`|\<|\>/g, '');
product_change_event = product_change_event + `add-cart-selected-variant-${opt.id}.rerender(data=event.selectedValues.${opt.name});`;
}
return `
${function() {
const isDarkBg = data.isDarkBg;
const textColor = isDarkBg ? '#ffffff' : '#212B36';
const delPriceColor = isDarkBg ? '#E0E0E2' : '#9CA0B0';
const variantColor = isDarkBg ? '#ffffff' : '#939393';
const config = data.config;
return `
`
}()}
${function(){
if(data.is_button_click_info) {
return `
${data.config.button_text || 'Add to cart'}
${data.config.button_text || 'Add to cart'}
`
} else {
return `
`
}
}()}
${function() {
if(data.is_button_click_info) {
return `
${function(){
let product_change_event = '';
const product_options = data.product.options?.filter(Boolean) || [];
for (let opt of product_options) {
const nameEscape = opt.name.replace(/\/|\\|\s|\'|\"|`|\<|\>/g, '');
product_change_event = product_change_event + `add-cart-selected-variant-${opt.id}.rerender(data=event.selectedValues.${opt.name});`;
}
return `
`
}()}
`
} else {
return ``
}
}()}
`
}()}
const TAG = 'spz-custom-add-to-cart';
class SpzCustomAddToCart extends SPZ.BaseElement {
constructor(element) {
super(element);
this.plugin_timestamp = Date.now();
this.defaultColorConfig = {
module_bg: "#FFFFFF",
button_bg: "#E84926",
button_color: "#FFFFFF",
text_color: "#202020",
price_color: "#E84926",
border_color: "#E6E6E6",
border_bg: "#FFFFFF",
round_size: '4'
};
this.config = this.defaultColorConfig;
this.originStickyTop = 0;
this.qty = 1;
this.variantId = this.element.getAttribute('variant-id');
this.trackMap = {
qty: this.trackQty.bind(this),
variant: this.trackVariant.bind(this),
addToCart: this.trackAddtoCart.bind(this),
atcView: this.trackAtcView.bind(this),
}
this.isHasView = false;
this.isFirstUpdateVariant=false;
}
static deferredMount() {
return false;
}
buildCallback() {
this.action_ = SPZServices.actionServiceForDoc(this.element);
this.templates_ = SPZServices.templatesForDoc(this.element);
this.xhr_ = SPZServices.xhrFor(this.win);
this.locale_ = SPZServices.localeFor(this.win);
this.hasGlobalAtcListener = !!document.querySelector('#add-cart-event-proxy');
this.setupAction_();
}
mountCallback() {
// 初始化
this.init();
}
async init() {
// 如果不是详情页,不需要执行后面js
if (window.SHOP_PARAMS.template_type !== '1') return;
await this.setLocale();
this.handleIsRender();
this.bingEvents();
}
bingEvents() {
// 设备切换 重新渲染
window.addEventListener('resize',
SPZCore.Types.debounce(
this.win,
(invocation) => {
// 关闭弹窗, 解决切换屏幕尺寸不能滚动的问题
this.triggerEvent_('closeShopModal');
this.config = {
...this.config,
isMobile: window.innerWidth < 768 ? true : false,
position: window.innerWidth < 768 ? 'down' : this.config.display_position
};
this.renderAddToCart();
},
200
))
}
// 获取多语言
async setLocale() {
let data;
try {
//多语言
data = await this.locale_.i18n(['product', 'products']);
} catch (error) {
console.error(error);
}
this.i18n = {
'sold_out': 'Sold out',
'add_to_cart': 'Add to cart',
'unavailable': 'Unavailable',
'product_unavailable': 'Product is unavailable.',
...data?.product?.product_detail,
...data?.products?.product,
}
}
getProductData() {
let pJson = document.querySelector('#product-json');
if (pJson) {
return JSON.parse(pJson.innerHTML)?.product;
} else if (typeof $ === 'function') {
return $(document).data('djproduct')?.product;
}
return undefined;
}
getAddCartBtn() {
return document.querySelector('[data-section-type="product_detail"] [data-click="addToCart"], [data-section-type="product_detail"] [role="addToCart"], [data-section-type="product_detail"] [data-click="buyNow"], [data-section-type="product_detail"] [role="buyNow"], [data-section-type="product"] [data-click="addToCart"], [data-section-type="product"] [role="addToCart"], [data-section-type="product"] [data-click="buyNow"], [data-section-type="product"] [role="buyNow"]');
}
handleObserver() {
if(this.config.trigger_condition == 'theme_hidden') {
const $addCartBtn = this.getAddCartBtn();
if($addCartBtn) {
// 配置了加购/购买按钮; 设置按钮为observer观察目标
$addCartBtn.setAttribute('id', 'app-atc-need-sticky-buttons')
}
}
}
findAncestor(node, selector) {
while (node) {
if (node.querySelector(selector)) {
return node;
}
node = node.parentElement;
}
return null;
}
getThemeProductInfoForm() {
let $themeForm = '';
const $themeProductInfo = document.querySelector('[data-section-type="product_detail"], [data-section-type="product"]');
$themeForm = $themeProductInfo?.querySelector('form');
return $themeForm;
}
// 获取主题商品加购数量
getThemeQuantity() {
let $themeForm = this.getThemeProductInfoForm();
if($themeForm) {
const formData = new FormData($themeForm);
const quantity = formData.get('quantity');
return quantity;
} else {
return null;
}
}
getThemeInitVariantsData() {
const $themeForm = this.getThemeProductInfoForm();
const formData = new FormData($themeForm);
}
// 获取主题初始表单数据
getThemeProductFormData = () => {
const $themeForm = this.getThemeProductInfoForm();
if($themeForm) {
const form_data = new FormData($themeForm);
const form_data_format = {}
for (const [key, value] of form_data) {
form_data_format[key] = value;
}
return form_data_format;
} else {
return null;
}
}
brightnessByColor(c) {
let color = '' + c,
isHEX = c.indexOf('#') == 0,
isRGB = c.indexOf('rgb') == 0;
let r, g, b;
if (isHEX) {
var m = color.substr(1).match(color.length == 7 ? /(\S{2})/g : /(\S{1})/g);
if (m) {
(r = parseInt(m[0], 16)), (g = parseInt(m[1], 16)), (b = parseInt(m[2], 16));
}
}
if (isRGB) {
const m = color.match(/^rgba\((\d+),\s*(\d+),\s*(\d+),(\d+)\)$/);
if (m) {
(r = m[1]), (g = m[2]), (b = m[3]);
}
}
if (typeof r != 'undefined') return (r * 299 + g * 587 + b * 114) / 1000;
};
getSelectedVariant() {
const product = this.getProductData();
return product.variants.find(item => item.id == this.variantId);
}
setSelectedOption(product) {
const selectedVariant = this.getSelectedVariant();
const productData = Object.assign({}, product);
// 处理下拉选项
productData?.options?.forEach(option => {
option.selectList = [];
option.values?.forEach(value => {
let selectStatus = '';
selectedVariant?.options?.forEach(item => {
if(item.name == option.name && item.value == value) {
selectStatus = 'checked';
}
})
option.selectList.push({name: option.name, value: value, checked: selectStatus});
})
})
return productData;
}
getSelectedSkuText() {
const selectVariant = this.getSelectedVariant();
if(!selectVariant) {
return '';
}
const selectList = selectVariant.options;
for (var i = 0; i < selectList.length; i++) {
selectList[i].value = selectVariant[`option${i + 1}`];
}
const selectedSkuText = selectList
.map( item => {
return item.value;
})
.join('/');
return selectedSkuText;
}
// 是否展示 加购弹窗
getIsButtonClickInfo() {
const product = this.getProductData();
const isMultipleProduct = product.available && !product.has_only_default_variant; // 多款式商品
const isButtonClickInfo = this.config.isMobile && (this.config.style_mobile === "mb_simple" || this.config.style_mobile === "mb_circle") && this.config.button_click_mobile === 'info' && isMultipleProduct;
return isButtonClickInfo;
}
// 是否跟随主题的加购数量 isFollowThemeQty (移动端 simple、circle模版、 PC端 simple模版加购数量从主题获取)
getIsFollowThemeQty() {
const followThemeQtyMobile = (this.config.style_mobile === "mb_simple" || this.config.style_mobile === "mb_circle") && this.config.isMobile;
const followThemeQtyPc = this.config.style === "simple" && !this.config.isMobile;
const isFollowThemeQty = (followThemeQtyPc || followThemeQtyMobile) ? true : false;
return isFollowThemeQty;
}
getBannerRenderData() {
const product = this.getProductData();
// 当前选中变体
const selectedVariant = this.getSelectedVariant();
// 变体options下拉列表数据处理
const productData = this.setSelectedOption(product);
// 选中的sku文案
const selectedSkuText = this.getSelectedSkuText();
// 是否展示 加购弹窗
const is_button_click_info = this.getIsButtonClickInfo();
// 主题是否有配置加购/购买按钮
const isHasAddCartBtn = this.getAddCartBtn() ? true : false;
// 加购数量是否跟随主题
const isFollowThemeQty = this.getIsFollowThemeQty();
// 浅色背景颜色配置
const brightness = this.brightnessByColor(this.config.module_bg);
const isDarkBg = brightness < 115;
const renderData = {
product: {
...productData,
config: {...this.config},
selectedVariant,
selectedSkuText,
},
config: this.config,
qty: this.qty,
variant: selectedVariant,
selectedVariant,
selectedSkuText,
is_button_click_info,
isDarkBg,
isHasAddCartBtn,
isFollowThemeQty,
i18n: this.i18n,
hasGlobalAtcListener: this.hasGlobalAtcListener
};
return renderData;
}
// render add_to_cart
async renderAll() {
const renderData = this.getBannerRenderData();
this.triggerEvent_('renderBanner', renderData);
}
getConfigData() {
return fetch(`/api/add-to-cart-config`).then(res => {
return res.json();
});
}
async getConfig() {
if(!this.config.hasOwnProperty('open_status')) {
const res = await this.getConfigData();
if (res.state === 0 && res.data.open_status) {
// config数据转化
this.transConfigData(res.data);
}
}
return this.config;
}
transConfigData(data) {
const resConfig = data;
this.config = {
...this.config,
...resConfig,
isMobile: window.innerWidth < 768 ? true : false,
position: window.innerWidth < 768 ? 'down' : resConfig.display_position
};
// 颜色跟随系统时
if(resConfig.color_setting === 'default' || resConfig.color_setting === 'theme') {
this.config = {
...this.config,
...this.defaultColorConfig
}
}
}
// 是否应用到商店
async handleIsRender() {
const res = await this.getConfigData();
if (res.state === 0 && res.data.open_status) {
// config数据转化
this.transConfigData(res.data);
this.handleObserver();
// 渲染加购内容
this.renderAddToCart();
}
}
renderAddToCart() {
const product = this.getProductData();
if (product.product_type === 'gift_card') return ;
if (!product.available) return;
this.renderAll();
}
// 选择变体 上报
trackVariant() {
const product = this.getProductData();
window.sa &&
sa.track('plugin_atc_variant_click', {
product_id: product.id,
style: this.config.style,
style_mobile: this.config.style_mobile
});
}
// 更改数量上报
trackQty() {
const product = this.getProductData();
window.sa &&
sa.track('plugin_atc_qty_click', {
product_id: product.id,
style: this.config.style,
style_mobile: this.config.style_mobile
});
}
// 加购上报
trackAddtoCart() {
const product = this.getProductData();
let properties = '';
let source = 'atc';
if (this.config.button_action === 'checkout') {
source = 'buy_now';
}
const obj = {
product_id: product.id,
style: this.config.style,
style_mobile: this.config.style_mobile,
button_action: this.config.button_action,
plugin_timestamp: this.plugin_timestamp
};
const options = {
product_id: product.id,
variant_id: this.variantId,
quantity: this.qty,
source,
variant: this.getSelectedVariant(),
product: product,
process: (window.SHOP_PARAMS.product_settings || {}).add_to_cart_process,
properties: properties
}
// 注册曝光参数到add_to_cart事件
window.spzutm && window.spzutm.registerParams('add_to_cart', { add_to_cart: JSON.stringify(obj) });
window.sa && sa.track('plugin_atc_button_click', obj);
this.trackHookAddTocart();
}
trackAtcView() {
const product = this.getProductData()
if(!this.isHasView) {
sa.track('plugin_atc_view', {
product_id: product.id,
button_action: this.config.button_action,
style: this.config.style,
style_mobile: this.config.style_mobile,
plugin_timestamp: this.plugin_timestamp
});
this.isHasView = true;
}
}
trackHookAddTocart(options) {
//FLASH及之后主题trigger事件上报采用HOOK
window.djInterceptors &&
window.djInterceptors.track &&
window.djInterceptors.track.use({
event: 'dj.addToCart',
params: {
id: options.product_id,
number: options.quantity,
childrenId: options.variant.id,
item_price: options.variant.price,
name: options.product.title,
type: options.variant.type ? options.variant.type : options.product.type,
properties: properties,
quantity: options.quantity,
variant_id: options.variant.id,
product_id: options.product_id,
source: 'atc',
process: options.process
},
once: true
});
}
// 加购弹窗
renderShopModal_() {
const renderData = this.getBannerRenderData();
this.triggerEvent_('showAddToCartModal', renderData);
}
showSuccessToast() {
this.triggerEvent_('showAddToCartToast');
}
updateSelectVariant = async(selectedVariant, product) => {
const configData = await this.getConfig();
const productData = this.setSelectedOption(product);
const selectedSkuText = this.getSelectedSkuText();
const renderData = {
...productData,
selectedVariant,
config: configData,
selectedSkuText
};
this.triggerEvent_('variantChange', renderData);
const renderBannerData = this.getBannerRenderData();
if(renderBannerData.is_button_click_info && this.config.style_mobile === "mb_circle") {
this.triggerEvent_('renderBannerClickInfo', renderBannerData);
}
}
getUrlVariantId() {
const urlParams = new URLSearchParams(window.location.search);
const variantId = urlParams.get('variant');
return variantId;
}
// 商详页变体切换
djVariantChange(detail) {
const product = this.getProductData();
// 是否为商详页当前商品
const isDetailProduct = product?.id === detail?.product?.id;
if(!detail || !isDetailProduct) {
return;
}
const selectVariant = detail.selected;
// 更新变体id
const variantId = selectVariant.id;
const oldVariantId = this.variantId;
if(variantId) {
this.triggerEvent_('renderSelectedVariant');
if(oldVariantId !== variantId || !this.isFirstUpdateVariant) {
this.isFirstUpdateVariant = true;
this.variantId = variantId;
this.updateSelectVariant(selectVariant, detail.product);
}
} else if(!variantId){
// 主题未选中变体
const isDisabledAtc = this.disabledAtc();
if(isDisabledAtc) {
this.triggerEvent_('renderBannerNoSelectedVariant', { product: detail.product });
}
}
}
// 不展示变体的模版
isHiddenVariantsTemplate() {
return ((this.config.style_mobile === 'mb_circle' || this.config.style_mobile === 'mb_simple') && this.config.isMobile) || (this.config.style === 'simple' && !this.config.isMobile);
}
// 主题未选中变体
isUnSelectedThemeVariants() {
// 不展示变体的模版
const formData = this.getThemeProductFormData();
return (formData && formData.hasOwnProperty('variant_id') && (!formData.variant_id || formData.variant_id === 'undefined'));
}
// 禁用加购
disabledAtc() {
// 不展示变体的模版
const isHiddenVariantsTemplate = this.isHiddenVariantsTemplate();
const isUnSelectedThemeVariant = this.isUnSelectedThemeVariants();
return isHiddenVariantsTemplate && isUnSelectedThemeVariant;
}
// 滚动至主题变体处
scrollToThemeVariant() {
const themeFormEl = this.getThemeProductInfoForm();
if(themeFormEl) {
this.triggerEvent_('showUnSelectedVariantTips');
setTimeout(() => {
themeFormEl.scrollIntoView({ behavior: 'smooth' });
}, 1000);
}
}
async setupAction_() {
// 上报处理
this.registerAction('saTrack', async(invocation) => {
const data = invocation.args;
// this.trackMap[data.type]?.();
})
// 更新variantId
this.registerAction('updateVriantId', async(invocation) => {
const data = invocation.args.data;
if(data.variant_id && data.variant_id != 'undefined') {
this.variantId = data.variant_id;
}
})
// 渲染弹窗加购&购买按钮
this.registerAction('renderShopModal', async(invocation) => {
this.renderShopModal_();
})
this.registerAction('bannerExtraRender', async(invocation) => {
const renderData = this.getBannerRenderData();
if(renderData.is_button_click_info && this.config.style_mobile === "mb_circle") {
this.triggerEvent_('renderBannerClickInfo', renderData);
}
})
// 加购数量同步
this.registerAction('handleAddToCart', async(invocation) => {
// 主题未选中变体
const isDisabledAtc = this.disabledAtc();
if(isDisabledAtc) {
this.scrollToThemeVariant();
return;
}
const quantity = this.getThemeQuantity();
if(this.config.button_action === "checkout" ) {
this.triggerEvent_('updateQty', { quantity: Number(quantity) || this.qty });
this.triggerEvent_('handleBuyNow');
} else {
this.triggerEvent_('updateQty', { quantity: Number(quantity) || this.qty });
this.triggerEvent_('handleAtc');
}
})
// 渲染弹窗加购&购买按钮
this.registerAction('handleFixedBanner', async(invocation) => {
// 固定定位时, 触发ljs-sticky 组件showSticky方法,展示banner
if(this.config.trigger_condition == 'fixed') {
this.triggerEvent_('showBanner');
// this.trackAtcView();
}
})
this.registerAction('handleVariantsRender', async(invocation) => {
const data = invocation.args.data;
// 主题未选中变体
const isDisabledAtc = this.disabledAtc();
if(isDisabledAtc) {
this.triggerEvent_('renderBannerNoSelectedVariant', { product: data });
return;
}
this.triggerEvent_('renderSelectedVariant', { product: data });
this.triggerEvent_('variantChange', data);
})
this.registerAction('handleShowErrorToast', async(invocation) => {
const data = invocation.args.data;
if(data?.message) {
this.triggerEvent_('showAtcErrorToast', data);
}
})
}
triggerEvent_(name, data) {
const event = SPZUtils.Event.create(this.win, `${TAG}.${name}`, data || {});
this.action_.trigger(this.element, name, event);
}
isLayoutSupported(layout) {
return layout == SPZCore.Layout.LOGIC;
}
}
SPZ.defineElement(TAG, SpzCustomAddToCart)