plus size for men
26
Products
${function(){
const product_total = data.total
if(product_total <= 1){
return `${product_total} Product`
}
return `${product_total} Products`
}()}
Filter
${item.label}
(${item.count})
${("The highest price is {{ highest_price }}").replace(/\{\{\s*highest_price\s*\}\}/, ``)}
Sort by
(function () {
const ACCORDION_ATTR = 'accordion';
const EXPANDED_ATTR = 'expanded';
const HEADER_ATTR = 'custom-accordion-header';
const CONTENT_ATTR = 'custom-accordion-content';
class SPZCutomAccordion extends SPZ.BaseElement {
domEventList = [];
initExpand = false;
isAccordion = false;
constructor(element) {
super(element);
this.xhr_ = SPZServices.xhrFor(this.win);
this.action_ = SPZServices.actionServiceForDoc(this.element);
}
isLayoutSupported(layout) {
return true;
}
buildCallback() {
this.setupAction_();
this.isAccordion = this.element.hasAttribute(ACCORDION_ATTR);
}
setupAction_() {
this.registerAction('update', (invocation) => {
this.update_();
});
}
mountCallback() {
this.registerDOMEvents();
}
update_() {
this.registerDOMEvents();
}
queryHeaderList() {
return SPZCore.Dom.scopedQuerySelectorAll(
this.element,
`[${HEADER_ATTR}]`
);
}
queryContentList() {
return SPZCore.Dom.scopedQuerySelectorAll(
this.element,
`${CONTENT_ATTR}`
);
}
queryHeaderNextContent(el) {
let loopTarget = el.nextElementSibling;
let contentEl = null;
do {
if (!loopTarget) {
break;
}
if (loopTarget.hasAttribute(CONTENT_ATTR)) {
contentEl = loopTarget;
break;
}
loopTarget = loopTarget.nextElementSibling;
} while(loopTarget && contentEl);
return contentEl;
}
updateContentElHeight(el) {
const isExpanded = el.hasAttribute(EXPANDED_ATTR);
let height = 0;
[...el.children].forEach(subEl => {
if (subEl.offsetHeight) {
height += subEl.offsetHeight;
}
});
if (isExpanded) {
el.style.height = height + 'px';
} else {
el.style.height = 0;
}
}
headerClickCallback(e) {
let selfEl = e.target;
let loopMaxDepth = 20;
let deep = 1;
while (!selfEl.hasAttribute(HEADER_ATTR) && deep <= loopMaxDepth) {
selfEl = selfEl.parentElement;
++deep;
}
if (!selfEl.hasAttribute(HEADER_ATTR)) {
return;
}
selfEl.toggleAttribute(EXPANDED_ATTR);
const contentEl = this.queryHeaderNextContent(selfEl);
if (contentEl) {
contentEl.toggleAttribute(EXPANDED_ATTR);
this.updateContentElHeight(contentEl);
}
if (!this.isAccordion) {
return;
}
const allContentEl = this.queryContentList();
allContentEl.forEach(content => {
if (content === contentEl) {
return;
}
content.toggleAttribute(EXPANDED_ATTR, false);
this.updateContentElHeight(content);
});
}
initDefaultExpand() {
if (this.initExpand) {
return;
}
const headerList = this.queryHeaderList();
const firstHeader = headerList[0];
if (!firstHeader) {
return;
}
const firstContent = this.queryHeaderNextContent(firstHeader);
if (!firstContent) {
return;
}
firstHeader.toggleAttribute(EXPANDED_ATTR, true);
firstContent.toggleAttribute(EXPANDED_ATTR, true);
this.initExpand = true;
}
registerDOMEvents() {
const callback = this.headerClickCallback.bind(this);
this.queryHeaderList().forEach(dom => {
// found exist event callback, do not re-bind
const recordIndex = this.domEventList.findIndex(de => de.el === dom);
if (recordIndex !== -1) {
return;
}
dom.addEventListener('click', callback);
this.domEventList.push({
el: dom,
cb: callback
});
});
}
}
SPZ.defineElement('spz-custom-accordion', SPZCutomAccordion);
})()
${function() {
const values = ['manual', 'published-descending', 'best-selling', 'add_to_cart_count', 'views'];
const recordData = data.data || 'manual';
const i18n = {
'manual': 'Recommend',
'published-descending': 'Newest in',
'best-selling': 'Highest sales',
'add_to_cart_count': 'Most purchased',
'views': 'Most viewed'
};
const checkedIcon = ``;
const snippets = values.map(v => {
const checked = recordData === v ? 'checked' : '';
const suffix = recordData === v ? checkedIcon : '';
return `${i18n[v]}${suffix}`;
}).join('');
return ``;
}()}
(function () {
const DEFAULT_ATTR = 'default';
const SELECT_ATTR = 'select';
const MATCH_QUERY = ['manual', 'published-descending', 'best-selling', 'add_to_cart_count', 'views'];
class SPZCustomMobileSortEntry extends SPZ.BaseElement {
templates_ = null;
templateBuilt = null;
defaultValue = null;
selectId = null;
active = false;
selectedValue = null;
constructor(element) {
super(element);
this.xhr_ = SPZServices.xhrFor(this.win);
this.action_ = SPZServices.actionServiceForDoc(this.element);
}
isLayoutSupported(layout) {
return true;
}
updateSelectValue(value) {
const selectDOM = document.getElementById(this.selectId);
if (!selectDOM) {
return;
}
selectDOM.value = value;
}
async updateTemplate(data) {
const el = await this.templates_.findAndRenderTemplate(this.element, data);
this.templateBuilt.innerHTML = el.innerHTML;
return el;
}
buildCallback() {
this.setupAction_();
this.templates_ = SPZServices.templatesForDoc(this.element);
this.defaultValue = this.element.getAttribute(DEFAULT_ATTR);
this.selectedValue = this.defaultValue;
this.selectId = this.element.getAttribute(SELECT_ATTR);
}
mountCallback() {
const urlParams = SPZUtils.Urls.parseQueryString(location.search);
const urlSortBy = urlParams.sort_by;
if (urlSortBy) {
if (MATCH_QUERY.includes(urlSortBy)) {
this.active = true;
this.defaultValue = urlSortBy;
this.selectedValue = urlSortBy;
}
}
this.templates_.findAndRenderTemplate(this.element, {
active: this.active,
value: this.defaultValue
}).then(el => {
this.element.appendChild(el);
this.templateBuilt = el;
this.trigger_('init', this.defaultValue);
});
}
setupAction_() {
this.registerAction('update', (invocation) => {
this.update(invocation.args.value);
});
this.registerAction('reset', (invocation) => {
this.reset();
});
}
update(value) {
this.active = true;
this.selectedValue = value;
this.updateTemplate({
active: true,
value
}).then(el => {
this.updateSelectValue(value);
this.trigger_('change', value);
});
}
reset() {
this.active = false;
this.updateTemplate({
active: false,
value: this.selectedValue,
}).then(el => {
this.trigger_('reset');
});
}
trigger_(name, data) {
const event = SPZUtils.Event.create(this.win, 'spz-custom-mobile-sort-entry.${name}', {
data
});
this.action_.trigger(this.element, name, event);
}
}
SPZ.defineElement('spz-custom-mobile-sort-entry', SPZCustomMobileSortEntry);
}())
(function() {
const SELECT_ATTR = 'select';
const SORT_ASC = 'price-ascending';
const SORT_DESC = 'price-descending';
const SORT_INIT = null;
const MATCH_QUERY = [SORT_ASC, SORT_DESC];
const LOOP_VALUE = [SORT_INIT, SORT_ASC, SORT_DESC];
class SPZCustomMobilePriceSort extends SPZ.BaseElement {
sort = SORT_INIT;
constructor(element) {
super(element);
this.xhr_ = SPZServices.xhrFor(this.win);
this.action_ = SPZServices.actionServiceForDoc(this.element);
this.templates_ = null;
this.templateBuilt = null;
this.selectId = null;
}
isLayoutSupported(layout) {
return true;
}
buildCallback() {
this.setupAction_();
this.templates_ = SPZServices.templatesForDoc(this.element);
this.selectId = this.element.getAttribute(SELECT_ATTR);
}
mountCallback() {
const urlParams = SPZUtils.Urls.parseQueryString(location.search);
const urlSortBy = urlParams.sort_by;
if (urlSortBy) {
if (MATCH_QUERY.includes(urlSortBy)) {
this.sort = urlSortBy;
}
}
this.templates_.findAndRenderTemplate(this.element, { value: this.sort }).then(el => {
this.element.appendChild(el);
this.templateBuilt = el;
});
}
setupAction_() {
this.registerAction('update', (invocation) => {
this.update();
});
this.registerAction('reset', (invocation) => {
this.reset();
});
}
updateSelectValue(value) {
const selectDOM = document.getElementById(this.selectId);
if (!selectDOM) {
return;
}
selectDOM.value = value;
}
async updateTemplate(value) {
const el = await this.templates_.findAndRenderTemplate(this.element, { value });
this.templateBuilt.innerHTML = el.innerHTML;
return el;
}
toggleSortValue() {
const currentIndex = LOOP_VALUE.findIndex(lv => lv === this.sort);
const nextIndex = currentIndex + 1;
const nextValueIndex = nextIndex % LOOP_VALUE.length;
return LOOP_VALUE[nextValueIndex];
}
update() {
const nextValue = this.toggleSortValue();
this.sort = nextValue;
this.updateTemplate(nextValue).then(el => {
this.updateSelectValue(nextValue);
this.trigger_('change', nextValue);
});
}
reset() {
this.sort = SORT_INIT;
this.updateTemplate(SORT_INIT).then(el => {
this.trigger_('reset');
});
}
trigger_(name, data) {
const event = SPZUtils.Event.create(this.win, 'spz-custom-mobile-price-sort.${name}', {
data
});
this.action_.trigger(this.element, name, event);
}
}
SPZ.defineElement('spz-custom-mobile-price-sort', SPZCustomMobilePriceSort);
}())
${function() {
const i18n = {
'manual': 'Recommend',
'published-descending': 'Newest in',
'best-selling': 'Highest sales',
'add_to_cart_count': 'Most purchased',
'views': 'Most viewed'
};
const act = data.active ? 'active' : '';
return `
${i18n[data.value || 'manual']}
`;
}()}
${function() {
let icon = '';
let isActive = true;
switch (data.value) {
case 'price-ascending':
icon = `
`;
break;
case 'price-descending':
icon = `
`;
break;
default:
isActive = false;
icon = `
`;
break;
}
const act = isActive ? 'active' : '';
return `
Price
${icon}
`;
}()}
${(function(){
const get_random_six_digits = () => {
return Math.random().toString().slice(-6)
};
const wholesale_enabled = false;
const setting_product_image_display = "133.33%";
const product_image = data.image;
const secondary_image = data.secondImage;
const image_width = product_image.width | 600;
let image_height = product_image.height | 600;
if(setting_product_image_display == '100%'){
image_height = image_width
}else if(setting_product_image_display == '133.33%'){
image_height = image_width * 1.3333;
};
let product_image_hover_on = false;
const has_save_label = true && ((+data.compare_at_price) > (+data.price));
const is_single_variant = data.variants.length == 1;
const min_price_variant_href = (data.min_price_variant && data.min_price_variant.available) ? data.min_price_variant.withinUrl : data.withinUrl;
const retail_price_max = data.retail_price_max || data.compare_at_price_max;
const THUMBNAILS_MAX_SIZE = 3;
const thumbnails = data.thumbVariants.slice(0, THUMBNAILS_MAX_SIZE);
const image_wrap_id = 'image_wrap_' + get_random_six_digits();
const image_carousel_id = 'image_carousel_' + get_random_six_digits();
const thumbnails_selector_id = 'thumbnails_selector_' + get_random_six_digits();
const form_id = 'form_' + get_random_six_digits() + "1539149755240-pc";
const mixed_wholesale = data.mixed_wholesale;
return `
`
})()}
No products found
Use fewer filters or
clear all
${(function(){
const get_random_six_digits = () => {
return Math.random().toString().slice(-6)
};
const wholesale_enabled = false;
const setting_product_image_display = "133.33%";
const product_image = data.image;
const secondary_image = data.secondImage;
const image_width = product_image.width | 600;
let image_height = product_image.height | 600;
if(setting_product_image_display == '100%'){
image_height = image_width
}else if(setting_product_image_display == '133.33%'){
image_height = image_width * 1.3333;
};
let product_image_hover_on = false;
const has_save_label = true && ((+data.compare_at_price) > (+data.price));
const is_single_variant = data.variants.length == 1;
const min_price_variant_href = (data.min_price_variant && data.min_price_variant.available) ? data.min_price_variant.withinUrl : data.withinUrl;
const retail_price_max = data.retail_price_max || data.compare_at_price_max;
const THUMBNAILS_MAX_SIZE = 3;
const thumbnails = data.thumbVariants.slice(0, THUMBNAILS_MAX_SIZE);
const image_wrap_id = 'image_wrap_' + get_random_six_digits();
const image_carousel_id = 'image_carousel_' + get_random_six_digits();
const thumbnails_selector_id = 'thumbnails_selector_' + get_random_six_digits();
const form_id = 'form_' + get_random_six_digits() + "1539149755240-mob";
const mixed_wholesale = data.mixed_wholesale;
return `
`
})()}
No products found
Use fewer filters or
clear all
${function() {
return `
${(data.index + 1) }/${data.total}
`
}()}
${function() {
return `
${(data.index + 1) }/${data.total}
`
}()}
(function() {
const STATUS = {
LOADING: 'loading',
FINISH: 'finish'
};
const RESULT = {
EMPTY: 'data-empty'
};
class SPZCustomCartSku extends SPZ.BaseElement {
renderData = [];
/**
* add to cart reselect item, and delete process:
* 1. record reselect id before show reselect modal
* 2. add to cart success, mark `needDeleteReselectRecord` to true
* 3. close reselect modal / drawer
* 4. spz-cart re-render, triggered mounted event
* 5. call refresh
*/
// mark delete reselect id
recordReselectId = null;
// mark should delete reselect record after spz-cart mounted event
needDeleteReselectRecord = false;
refreshLock = false;
addToCartSuccess = false;
// cache paused refresh data
refreshDataCache = [];
// cache similar products data, for manual add to cart
similarData = [];
constructor(element) {
super(element);
this.xhr_ = SPZServices.xhrFor(this.win);
this.action_ = SPZServices.actionServiceForDoc(this.element);
}
setupAction_() {
this.registerAction('refresh', (invocation) => {
const data = invocation && invocation.args && invocation.args.data || {};
this.refresh(data);
});
this.registerAction('deleteReselect', SPZCore.Types.debounce(
this.win,
(invocation) => {
this.deleteReselect(invocation?.args?.id);
},
200
));
this.registerAction('deleteInvalid', SPZCore.Types.debounce(
this.win,
(invocation) => {
this.deleteInvalid(invocation?.args?.id);
},
200
));
this.registerAction('setReselectRecordId', (invocation) => {
this.setReselectRecordId(invocation?.args?.id);
});
this.registerAction('clearNeedReselectRecord', (invocation) => {
this.clearNeedReselectRecord();
});
this.registerAction('registerDeleteReselectTask', (invocation) => {
this.registerDeleteReselectTask(invocation?.args?.data);
});
this.registerAction('lockRefresh', (invocation) => {
this.lockRefresh();
});
this.registerAction('releaseRefreshLock', (invocation) => {
this.releaseRefreshLock();
});
this.registerAction('manualAddToCart', (invocation) => {
this.manualAddToCart(invocation?.args?.id);
});
this.registerAction('syncSimilarData', (invocation) => {
this.syncSimilarData(invocation?.args?.data);
});
// DEBUG
this.registerAction('log', (invocation) => {
const data = invocation && invocation.args && invocation.args.data || {};
console.log('log', invocation);
});
}
buildCallback() {
this.setupAction_();
}
isLayoutSupported(layout) {
return true;
}
/**
* 数据去重
* @param {Array} data
*/
uniq_(data) {
const result = [];
for(let i = 0; i < data.length; i++) {
if(!result.includes(data[i])) {
result.push(data[i]);
}
}
return result;
}
checkRefreshLock() {
if (this.refreshLock) {
return false;
}
return true;
}
setLoading(isLoading) {
SPZCore.Dom.toggleAttribute(this.element, STATUS.LOADING, isLoading);
SPZCore.Dom.toggleAttribute(this.element, STATUS.FINISH, !isLoading);
}
setDataResult(data) {
const isDataEmpty = !data.reselectSkus.length && !data.invalidSkus.length && !data.line_items.length;
SPZCore.Dom.toggleAttribute(this.element, RESULT.EMPTY, isDataEmpty);
}
// 存在多次请求顺序问题
async refresh(_data) {
// 接口失败时返回数据不为对象
if (!(typeof _data === 'object' && _data.line_items && _data.line_items.length > 0)) {
const newData = {
line_items: [],
reselectSkus: [],
invalidSkus: [],
displayInvalidSkus: []
};
this.trigger_('refreshError', newData);
this.setLoading(false);
return;
}
if (!this.checkRefreshLock()) {
this.setRefreshCache(_data);
return;
}
this.setLoading(true);
let data = _data;
if (this.needDeleteReselectRecord && this.recordReselectId) {
let hasError = false;
try {
data = await this.deleteReselectRecordBeforeRefresh(_data);
} catch (e) {
hasError = true;
console.error(e);
const newData = {
...data,
reselectSkus: [],
invalidSkus: [],
displayInvalidSkus: []
};
this.trigger_('refreshError', newData);
this.setLoading(false);
this.setDataResult(newData);
return;
}
this.needDeleteReselectRecord = false;
if (hasError) {
return;
}
}
// 获取失效商品
const invisibleItems = data.ineffectives;
// 获取失效商品内仅售罄商品的sku
const soldOutItems = invisibleItems.filter(item => item.reason === "line_item_sold_out");
// 通过失效 sku 获取 spu, 注意去重
const soldOutSpuIds = this.uniq_(soldOutItems.map(item => item.product_id));
const querySpuIds = soldOutSpuIds.map(spuId => `ids[]=${spuId}`).join('&');
if (!soldOutSpuIds.length) {
const newData = {
...data,
reselectSkus: [],
invalidSkus: [],
displayInvalidSkus: []
};
this.trigger_('refreshSuccess', newData);
this.renderData = newData;
this.setLoading(false);
this.setDataResult(newData);
return;
}
// 请求 spu 下其他 sku 库存
this.xhr_.fetchJson(`/api/product/list?limit=${soldOutSpuIds.length}&${querySpuIds}`)
.then(res => {
const reselectSkus = [];
const invalidSkus = [];
// spu 维度展示
const displayInvalidSkus = [];
res.data.list.forEach(product => {
// spu 匹配, 存在多 sku 情况
const soldOutSkus = soldOutItems.filter(item => item.product_id === product.id);
if (!soldOutSkus.length) {
return;
}
// 通过失效 sku 获取 spu 下其他 sku 库存, 存在其他可用库存时标记为 "Reselect" 按钮可用
//const allowReselect = product.variants
// .filter(variant => variant.option1 === soldOutProduct.variant.option1 && variant.option2 === soldOutProduct.variant.option2 && variant.option3 === soldOutProduct.variant.option3)
// .some(variant => variant.available);
// 查询售罄 sku 对应商品是否可加购, 标记为 Reselect 可用项
if (product.available) {
reselectSkus.push(...soldOutSkus);
// 若商品不可用(全部 sku 售罄), 归纳到失效商品列表
} else {
invalidSkus.push(...soldOutSkus);
// spu 维度, 仅添加一项 sku
displayInvalidSkus.push(soldOutSkus[0]);
}
});
const newData = {
...data,
reselectSkus,
invalidSkus,
displayInvalidSkus
};
this.trigger_('refreshSuccess', newData);
this.renderData = newData;
this.setLoading(false);
this.setDataResult(newData);
})
.catch(err => {
this.setLoading(false);
console.error(err);
this.trigger_('refreshError', data);
}).finally(() => {
});
}
async deleteReselect(id, ignoreEmit) {
if (!id) {
return;
}
const targetSku = this.renderData.reselectSkus.find(item => item.id === id);
try {
const res = await this.xhr_.fetchJson(`/api/cart/${targetSku.variant_id}`, {
method: 'DELETE',
body: {
id: targetSku.id,
product_id: targetSku.product_id,
variant_id: targetSku.variant_id,
}
});
const newData = {
...this.renderData,
reselectSkus: this.renderData.reselectSkus.filter(item => item.id !== id),
};
this.renderData = newData;
!ignoreEmit && this.trigger_('deleteSuccess', newData);
} catch (err) {
console.error(err);
!ignoreEmit && this.trigger_('deleteError', err);
}
}
deleteInvalid(id) {
if (!id) {
return;
}
const targetSku = this.renderData.invalidSkus.find(item => item.id === id);
this.xhr_.fetchJson(`/api/cart/${targetSku.variant_id}`, {
method: 'DELETE',
body: {
id: targetSku.id,
product_id: targetSku.product_id,
variant_id: targetSku.variant_id,
}
})
.then(res => {
const newData = {
...this.renderData,
invalidSkus: this.renderData.invalidSkus.filter(item => item.id !== id),
displayInvalidSkus: this.renderData.displayInvalidSkus.filter(item => item.id !== id),
};
this.trigger_('deleteSuccess', newData);
this.renderData = newData;
})
.catch(err => {
console.error(err);
this.trigger_('deleteError', err);
});
}
// record reselect sku id before show reselect modal
setReselectRecordId(id) {
if (!id) {
return;
}
this.recordReselectId = id;
}
async deleteReselectRecordBeforeRefresh(data) {
if (!this.recordReselectId) {
return;
}
await this.deleteReselect(this.recordReselectId, true);
return {
...data,
ineffectives: data.ineffectives.filter(item => item.id !== this.recordReselectId)
}
}
clearNeedReselectRecord() {
this.needDeleteReselectRecord = false;
}
registerDeleteReselectTask(productData) {
const targetSku = this.renderData.reselectSkus.find(item => item.id === this.recordReselectId);
if (targetSku?.variant_id && productData?.variant.id) {
if (targetSku.variant_id === productData?.variant.id) {
this.needDeleteReselectRecord = false;
return;
}
}
this.needDeleteReselectRecord = true;
}
// pause cart refresh(trigger by similar products)
lockRefresh() {
if (this.refreshLock) {
return;
}
this.refreshLock = true;
}
releaseRefreshLock() {
if (!this.refreshLock) {
return;
}
this.refreshLock = false;
this.refreshWithCache();
}
// direct add_to_cart(trigger by similar products)
async manualAddToCart(id) {
const target = this.similarData.find(item => item.id === id);
this.lockRefresh();
try {
const res = await this.xhr_.fetchJson(`/api/cart`, {
method: 'POST',
body: {
note: '',
product_id: target.id,
quantity: '1',
variant_id: target.variants[0].id,
refer_info: {
source: 'add_to_cart'
}
}
});
const newCartItems = res.data.items;
this.setRefreshCache(newCartItems, true);
} catch (err) {
console.error(err);
}
}
syncSimilarData(data) {
this.similarData = data.data;
}
setRefreshCache(data, patch) {
if (patch) {
this.refreshDataCache = {
...this.refreshDataCache,
line_items: [...this.refreshDataCache.line_items, ...data]
};
} else {
this.refreshDataCache = data;
}
}
refreshWithCache() {
this.refresh(this.refreshDataCache);
}
mountCallback() {
}
unmountCallback() {
}
/**
* trigger event
* @param {Object} data
*/
trigger_(name, data) {
const event = SPZUtils.Event.create(this.win, `spz-custom-cart-sku.${name}`, {
data,
});
this.action_.trigger(this.element, name, event);
}
}
SPZ.defineElement('spz-custom-cart-sku', SPZCustomCartSku);
}())
(function () {
class SPZCustomCartTrack extends SPZ.BaseElement {
constructor(element) {
super(element);
this.action_ = SPZServices.actionServiceForDoc(this.element);
}
isLayoutSupported(layout) {
return true;
}
buildCallback() {
this.setupAction_();
}
hash(val) {
const hashKey = Object.keys(val).sort((a, b) => a - b).reduce((acc, k) => {
acc += `{'${k}':'${val[k]}'}`;
return acc;
}, '');
return hashKey;
}
trackExtra(key, value) {
console.log('trackExtra...', key, value);
if (!window.sa) {
return;
}
const hashKey = this.hash(value);
let hasExtraInfo = false;
if (window.sa.eventInfo && window.sa.eventInfo[key] && window.sa.eventInfo[key].extra_properties) {
hasExtraInfo = window.sa.eventInfo[key].extra_properties.some(p