49%OFFCorrugated Suction Cup Storage Holde – Hand
/**
* ไผๆ ็ ็ปไปถๆจกๅ็ฑป
* ๅค็ไผๆ ็ ็ๆพ็คบๅไบคไบ้ป่พ
*/
class SpzCustomDiscountCodeModel extends SPZ.BaseElement {
constructor(element) {
super(element);
// ๅคๅถๆ้ฎๅๅ
ๅฎน็็ฑปๅ
this.copyBtnClass = "discount_code_btn"
this.copyClass = "discount_code_value"
}
isLayoutSupported(layout) {
return layout == SPZCore.Layout.LOGIC;
}
buildCallback() {
// ๅๅงๅๆๅก
this.action_ = SPZServices.actionServiceForDoc(this.element);
this.templates_ = SPZServices.templatesForDoc(this.element);
this.xhr_ = SPZServices.xhrFor(this.win);
}
/**
* ๆธฒๆไผๆ ็ ็ปไปถ
* @param {Object} data - ๆธฒๆๆฐๆฎ
*/
doRender_(data) {
return this.templates_
.findAndRenderTemplate(this.element,
Object.assign(this.getDefaultData(), data)
)
.then((el) => {
this.clearDom();
this.element.appendChild(el);
// ็ปๅฎๅคๅถไปฃ็ ๅ่ฝ
this.copyCode(el, data);
});
}
/**
* ่ทๅๆธฒๆๆจกๆฟ
* @param {Object} data - ๆธฒๆๆฐๆฎ
*/
getRenderTemplate(data) {
const renderData = Object.assign(this.getDefaultData(), data);
return this.templates_
.findAndRenderTemplate(this.element, renderData)
.then((el) => {
this.clearDom();
return el;
});
}
/**
* ๆธ
้คDOMๅ
ๅฎน
*/
clearDom() {
const children = this.element.querySelector('*:not(template)');
children && SPZCore.Dom.removeElement(children);
}
/**
* ่ทๅ้ป่ฎคๆฐๆฎ
* @returns {Object} ้ป่ฎคๆฐๆฎๅฏน่ฑก
*/
getDefaultData() {
return {
isMobile: appDiscountUtils.judgeMobile(),
isRTL: appDiscountUtils.judgeRTL(),
image_domain: this.win.SHOPLAZZA.image_domain,
copyBtnClass: this.copyBtnClass,
copyClass: this.copyClass
}
}
/**
* ๅคๅถไผๆ ็ ๅ่ฝ
* @param {Element} el - ๅฝๅๅ
็ด
*/
copyCode(el) {
const copyBtnList = el.querySelectorAll(`.${this.copyBtnClass}`);
if (copyBtnList.length > 0) {
copyBtnList.forEach(item => {
item.onclick = async () => {
// ็กฎไฟ่ทๅๆญฃ็กฎ็ๅ
็ด ๅๅ
ๅฎน
const codeElement = item.querySelector(`.${this.copyClass}`);
if (!codeElement) return;
// ่ทๅ็บฏๆๆฌๅ
ๅฎน
const textToCopy = codeElement.innerText.trim();
// ๅฐ่ฏไฝฟ็จ็ฐไปฃAPI๏ผๅฆๆๅคฑ่ดฅๅไฝฟ็จๅค็จๆนๆก
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(textToCopy);
} else {
throw new Error('Clipboard API not available');
}
// ๆพ็คบๅคๅถๆๅๆ็คบ
this.showCopySuccessToast(textToCopy, el);
} catch (err) {
console.error('Modern clipboard API failed, trying fallback...', err);
// ไฝฟ็จๅค็จๅคๅถๆนๆก
this.fallbackCopy(textToCopy, el);
}
const discountId = item.dataset["discountId"];
// ่ทณ่ฝฌๅณ็ญ: is_redirection + link(ๅฏ้่ฆ็)
const setting = {
is_redirection: item.dataset["redirection"] === "true",
link: item.dataset["link"],
};
const landingUrl = `/promotions/discount-default/${discountId}`;
const finalUrl = appDiscountUtils.resolveDiscountHref(setting, landingUrl);
if (finalUrl && appDiscountUtils.inProductBody(this.element)) {
this.win.open(finalUrl, '_blank', 'noopener');
}
}
})
}
}
/**
* ไฝฟ็จ execCommand ็ๅคๅถๆนๆก
* @param {string} codeText - ่ฆๅคๅถ็ๆๆฌ
* @param {Element} el - ๅฝๅๅ
็ด
*/
fallbackCopy(codeText, el) {
const textarea = this.win.document.createElement('textarea');
textarea.value = codeText;
// ่ฎพ็ฝฎๆ ทๅผไฝฟๆๆฌๆกไธๅฏ่ง
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
textarea.style.top = '0';
// ๆทปๅ readonly ๅฑๆง้ฒๆญข็งปๅจ็ซฏ่ๆ้ฎ็ๅผนๅบ
textarea.setAttribute('readonly', 'readonly');
this.win.document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
this.win.document.execCommand('copy');
// ๆพ็คบๅคๅถๆๅๆ็คบ
this.showCopySuccessToast(codeText, el);
} catch (err) {
console.error('Copy failed:', err);
}
this.win.document.body.removeChild(textarea);
}
/**
* ๅๅปบ Toast ๅ
็ด
* @returns {Element} ๅๅปบ็ Toast ๅ
็ด
*/
createToastEl_() {
const toast = document.createElement('ljs-toast');
toast.setAttribute('layout', 'nodisplay');
toast.setAttribute('hidden', '');
toast.setAttribute('id', 'discount-code-toast');
toast.style.zIndex = '1051';
return toast;
}
/**
* ๆ่ฝฝ Toast ๅ
็ด ๅฐ body
* @returns {Element} ๆ่ฝฝ็ Toast ๅ
็ด
*/
mountToastToBody_() {
const existingToast = this.win.document.getElementById('discount-code-toast');
if (existingToast) {
return existingToast;
}
const toast = this.createToastEl_();
this.win.document.body.appendChild(toast);
return toast;
}
/**
* ๅคๅถๆๅ็ๆ้
* @param {string} codeText - ่ฆๅคๅถ็ๆๆฌ
* @param {Element} el - ๅฝๅๅ
็ด
*/
showCopySuccessToast(codeText, el) {
const $toast = this.mountToastToBody_();
SPZ.whenApiDefined($toast).then(toast => {
toast.showToast("Discount code copied !");
this.codeCopyInSessionStorage(codeText);
});
}
/**
* ๅคๅถไผๆ ็ ๆๅๅ่ฆๅญไธไปฝๅฐๆฌๅฐๅญๅจไธญ๏ผ่ดญ็ฉ่ฝฆไฝฟ็จ
* @param {string} codeText - ่ฆๅคๅถ็ๆๆฌ
*/
codeCopyInSessionStorage(codeText) {
try {
sessionStorage.setItem('other-copied-coupon', codeText);
} catch (error) {
console.error(error)
}
}
}
// ๆณจๅ่ชๅฎไนๅ
็ด
SPZ.defineElement('spz-custom-discount-code-model', SpzCustomDiscountCodeModel);
/**
* Custom discount code component that handles displaying and managing discount codes
* @extends {SPZ.BaseElement}
*/
class SpzCustomDiscountCode extends SPZ.BaseElement {
constructor(element) {
super(element);
// API endpoint for fetching discount codes
this.getDiscountCodeApi = "\/api\/storefront\/promotion\/code\/list";
// Debounce timer for resize events
this.timer = null;
// Current variant ID
this.variantId = "76afed88-2468-462c-b274-5214a2964ad3";
// Store discount code data
this.discountCodeData = {}
}
/**
* Check if layout is supported
* @param {string} layout - Layout type
* @return {boolean}
*/
isLayoutSupported(layout) {
return layout == SPZCore.Layout.LOGIC;
}
/**
* Initialize component after build
*/
buildCallback() {
this.templates_ = SPZServices.templatesForDoc();
this.viewport_ = this.getViewport();
// Bind methods to maintain context
this.render = this.render.bind(this);
this.resize = this.resize.bind(this);
this.switchVariant = this.switchVariant.bind(this);
}
/**
* Setup component when mounted
*/
mountCallback() {
this.getData();
// Add event listeners
this.viewport_.onResize(this.resize);
this.win.document.addEventListener('dj.variantChange', this.switchVariant);
}
/**
* Cleanup when component is unmounted
*/
unmountCallback() {
this.viewport_.removeResize(this.resize);
this.win.document.removeEventListener('dj.variantChange', this.switchVariant);
// ๆธ
้คๅฎๆถๅจ
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
/**
* Handle resize events with debouncing
*/
resize() {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null;
}
this.timer = setTimeout(() => {
if (appDiscountUtils.inProductBody(this.element)) {
this.render();
} else {
this.renderSkeleton();
}
}, 200);
}
/**
* Handle variant changes
* @param {Event} event - Variant change event
*/
switchVariant(event) {
const variant = event.detail.selected;
if (variant.product_id == 'c445910a-7dec-47e1-b565-4b22935e328e' && variant.id != this.variantId) {
this.variantId = variant.id;
this.getData();
}
}
/**
* Fetch discount code data from API
*/
getData() {
if (appDiscountUtils.inProductBody(this.element)) {
const reqBody = {
product_id: "c445910a-7dec-47e1-b565-4b22935e328e",
variant_id: this.variantId,
product_type: "default",
}
if (!reqBody.product_id || !reqBody.variant_id) return;
this.discountCodeData = {};
this.win.fetch(this.getDiscountCodeApi, {
method: "POST",
body: JSON.stringify(reqBody),
headers: {
"Content-Type": "application/json"
}
}).then(async (response) => {
if (response.ok) {
let data = await response.json();
if (data.list && data.list.length > 0) {
data.list[0].product_setting.template_config = JSON.parse(data.list[0].product_setting.template_config);
// Format timestamps to local timezone
const zone = this.win.SHOPLAZZA.shop.time_zone;
data.list = data.list.map(item => {
if(+item.ends_at !== -1) {
item.ends_at = appDiscountUtils.convertTimestampToFormat(+item.ends_at, zone);
}
item.starts_at = appDiscountUtils.convertTimestampToFormat(+item.starts_at, zone);
return item;
});
}
this.discountCodeData = data;
this.render();
} else {
this.clearDom();
}
}).catch(err => {
console.error("discount_code", err)
this.clearDom();
});
} else {
this.renderSkeleton();
}
}
/**
* Clear component DOM except template
*/
clearDom() {
const children = this.element.querySelector('*:not(template)');
children && SPZCore.Dom.removeElement(children);
}
/**
* Render discount codes with formatted dates
*/
render() {
// Render using discount code model
SPZ.whenApiDefined(document.querySelector('#spz_custom_discount_code_model')).then(renderApi => {
renderApi.doRender_({
discountCodeData: this.discountCodeData
})
}).catch(err => {
this.clearDom();
})
}
renderSkeleton() {
// Render template for non-product pages
this.templates_
.findAndRenderTemplate(this.element, {
isMobile: appDiscountUtils.judgeMobile()
})
.then((el) => {
this.clearDom();
this.element.appendChild(el);
})
.catch(err => {
this.clearDom();
});
}
}
// Register custom element
SPZ.defineElement('spz-custom-discount-code', SpzCustomDiscountCode);
Style:
Rectangular
Please select a Style
Color:
Transparent
Please select a Color
Add to cart
$25.99
Buy now
Product was out of stock.
Product is unavailable.