/** * Copyright (C) 2020 Futurenext srl * * This file is part of Zakeke. * * Zakeke Interactive Product Designer can not be copied and/or distributed * without the express permission of Futurenext srl * * @author Futurenext srl * @copyright 2020 Futurenext srl * @license https://www.zakeke.com/privacy/#general_conditions */ function zakekeDesigner(config) { function partition(items, key) { return Object.values(items.reduce( (result, item) => ({ ...result, [item[key]]: [ ...(result[item[key]] || []), item, ], }), {}, )); } function uniqBy(a, key) { let seen = {}; return a.filter(item => { const k = key(item); return seen.hasOwnProperty(k) ? false : (seen[k] = true); }); } function productAttributes() { const formData = new FormData(); formData.append('id_product', config.params.id_product); return fetch(config.attributesEndpoint, { method: 'post', body: formData, }).then(res => res.json()) .then(res => { const attributes = partition(res.combinations.flat(), 'id_attribute_group') .map(options => uniqBy(options, option => option.id_attribute)) .map(options => ({ id: options[0].id_attribute_group, label: options[0].group_name, values: options.map(option => ({ id: option.id_attribute, label: option.attribute_name }) ) }), {}); const variants = res.combinations.map(options => options.map(option => ({ Id: option.id_attribute_group, Value: { Id: option.id_attribute } }))); return { attributes, variants }; }); } function emitProductAttributes(promiseId, attributes) { iframe.contentWindow.postMessage({ data: Object.assign({ promiseId }, attributes), zakekeMessageType: 2 }, '*'); } function updatedParams(color, zakekeOptions) { if (color == null) { throw new Error('color param is null'); } let params = Object.assign({}, config.params); const colorObj = JSON.parse(color); colorObj.forEach(function (val) { if (!params['group']) { params['group'] = {}; } params['group'][val.Id] = val.Value.Id; }); if (zakekeOptions != null) { params = Object.assign({}, params, zakekeOptions); } return params; } function productPrice(promiseId, zakekeOptions) { function emitProductPriceEvent(promiseId, productPrice) { iframe.contentWindow.postMessage({ data: Object.assign({ promiseId }, productPrice), zakekeMessageType: 3 }, '*'); } function processResponse(data) { if (data.errors) { return { isOutOfStock: true }; } else { return { isOutOfStock: !data.isInStock, finalPrice: data.finalPrice }; } } let params = zakekeOptions.params; params['zakeke-percent-price'] = zakekeOptions['zakeke-percent-price']; params['zakeke-price'] = zakekeOptions['zakeke-price']; params['total_qty'] = zakekeOptions['total_qty']; delete params['controller']; const queryString = jQuery.param(params); const cached = productDataCache[queryString]; if (cached !== undefined) { emitProductPriceEvent(promiseId, cached); return; } if (pendingProductDataRequests.indexOf(queryString) !== -1) { return; } pendingProductDataRequests.push(queryString); jQuery.ajax({ url: config.priceEndpoint, type: 'POST', headers: { Accept: 'application/json' }, data: params }) .done(response => { const responseData = processResponse(response); productDataCache[queryString] = responseData; emitProductPriceEvent(promiseId, responseData); }) .fail(() => { const productData = { isOutOfStock: true }; productDataCache[queryString] = productData; emitProductPriceEvent(promiseId, productData); }) .always(() => { const index = pendingProductDataRequests.indexOf(queryString); if (index !== -1) { pendingProductDataRequests.splice(index, 1); } }); } function productData(color, zakekeOptions) { function emitProductDataEvent(productData) { iframe.contentWindow.postMessage({ data: productData, zakekeMessageType: 1 }, iframeSrc); } function processResponse(data) { if (data.errors) { return { color, isOutOfStock: true }; } else { return { color, isOutOfStock: !data.isInStock, finalPrice: window.zakekePriceHideOnlyFrontend ? 0 : data.finalPrice }; } } let params = updatedParams(color, zakekeOptions); delete params['controller']; const queryString = jQuery.param(params); const cached = productDataCache[queryString]; if (cached !== undefined) { emitProductDataEvent(cached); return; } if (pendingProductDataRequests.indexOf(queryString) !== -1) { return; } pendingProductDataRequests.push(queryString); jQuery.ajax({ url: config.priceEndpoint, type: 'POST', headers: { Accept: 'application/json' }, data: params }) .done(response => { const responseData = processResponse(response); productDataCache[queryString] = responseData; emitProductDataEvent(responseData); }) .fail(() => { const productData = { color, isOutOfStock: true }; productDataCache[queryString] = productData; emitProductDataEvent(productData); }) .always(() => { const index = pendingProductDataRequests.indexOf(queryString); if (index !== -1) { pendingProductDataRequests.splice(index, 1); } }); } function addToCartAjax(zakekeOptions) { const params = zakekeOptions.params; const form = document.getElementById('zakeke-addtocart'); delete params['controller']; params['action'] = 'update'; params['add'] = '1'; return Promise.resolve(jQuery.ajax({ url: config.addEndpoint, type: 'POST', headers: { Accept: 'application/json' }, data: params })).then(response => { if (response.errors) { console.error("Error: " + JSON.stringify(response)); return; } params['id_customization'] = response.id_customization; delete params['fc']; delete params['module']; const formData = new FormData(form); Object.keys(params).forEach(key => { if (params[key] instanceof String || typeof (params[key]) !== 'object') { formData.set(key, params[key]) } else { Object.keys(params[key]).forEach(subKey => { formData.set(key + '[' + subKey + ']', params[key][subKey]) }); } }); return fetch(form.action, { method: 'POST', body: formData }); }); } function addToCart(color, design) { let params = updatedParams(color, { 'zakeke_design': design }); delete params['controller']; params['action'] = 'update'; params['add'] = '1'; const form = document.getElementById('zakeke-addtocart'); jQuery.ajax({ url: config.addEndpoint, type: 'POST', headers: { Accept: 'application/json' }, data: params }).done(response => { if (response.errors) { console.error('Error: ' + JSON.stringify(response)); return; } params['id_customization'] = response.id_customization; delete params['fc']; delete params['module']; Object.keys(params).forEach(key => { if (params[key] instanceof String || typeof (params[key]) !== 'object') { const input = document.createElement('INPUT'); input.type = 'hidden'; input.name = key; input.value = params[key]; form.appendChild(input); } else { Object.keys(params[key]).forEach(subKey => { const input = document.createElement('INPUT'); input.type = 'hidden'; input.name = key + '[' + subKey + ']'; input.value = params[key][subKey]; form.appendChild(input); }); } }); jQuery(form).submit(); }) .fail((request, status) => { console.error('Request addToCart failed: ' + status); }); } var productDataCache = {}, pendingProductDataRequests = [], container = document.getElementById('zakeke-container'), iframe = container.firstElementChild, iframeSrc = null; window.addEventListener('message', event => { if (event.origin !== config.zakekeUrl) { return; } if (event.data.zakekeMessageType === 0) { if (config.params.remove_from_cart_url) { jQuery.ajax(config.params.remove_from_cart_url).always(() => { addToCart(event.data.colorId, event.data.designId); }); } else { addToCart(event.data.colorId, event.data.designId, event.data.modelId); } } else if (event.data.zakekeMessageType === 1) { let zakekeOptions = {}; if (event.data.design.price !== undefined) { zakekeOptions['zakeke-price'] = event.data.design.price; } if (event.data.design.percentPrice !== undefined) { zakekeOptions['zakeke-percent-price'] = event.data.design.percentPrice; } productData(event.data.design.color, zakekeOptions); } else if (event.data.zakekeMessageType === 2) { productAttributes().then(attributes => emitProductAttributes(event.data.data.promiseId, attributes)); } else if (event.data.zakekeMessageType === 3) { let zakekeOptions = { params: Object.assign({}, config.params, { group: event.data.data.attributes.reduce((acc, val) => { acc[val.Id] = val.Value.Id; return acc; }, {}), qty: parseInt(event.data.data.quantity) }) }; if (event.data.data.price !== undefined) { zakekeOptions['zakeke-price'] = event.data.data.price; } if (event.data.data.percentPrice !== undefined) { zakekeOptions['zakeke-percent-price'] = event.data.data.percentPrice; } zakekeOptions['total_qty'] = event.data.data.totalQuantity; productPrice(event.data.data.promiseId, zakekeOptions); } else if (event.data.zakekeMessageType === 4) { const totalQty = event.data.data.attributes.reduce((acc, val) => acc + val.quantity, 0); function processAddToCartAjax(selections, i) { if (selections.length <= i) { window.location.assign(config.checkoutUrl); return Promise.resolve(); } const selection = selections[i]; const zakekeOptions = { params: Object.assign({}, config.params, { zakeke_design: event.data.data.designID, group: selection.attributes.reduce((acc, val) => { acc[val.Id] = val.Value.Id; return acc; }, {}), qty: selection.quantity, total_qty: totalQty }) }; return addToCartAjax(zakekeOptions).then(() => { return processAddToCartAjax(selections, i + 1); }); } return processAddToCartAjax(event.data.data.attributes, 0); } }, false); if (window.matchMedia('(min-width: 768px)').matches) { iframeSrc = config.customizerLargeUrl; } else { iframeSrc = config.customizerSmallUrl; document.body.appendChild(container); } iframe.src = iframeSrc; } if (document.readyState === 'complete' || document.readyState === 'loaded' || document.readyState === 'interactive') { zakekeDesigner(JSON.parse(window.zakekeDesignerConfig)); } else { document.addEventListener('DOMContentLoaded', () => { zakekeDesigner(JSON.parse(window.zakekeDesignerConfig)); }); }