Skip to content

Commit

Permalink
Merge branch 'next-12059/create-cross-selling-element' into 'master'
Browse files Browse the repository at this point in the history
NEXT-12059 - Create "Cross Selling" element

See merge request shopware/6/product/platform!3861
  • Loading branch information
taltholtmann committed Nov 30, 2020
2 parents 2bebd25 + c009658 commit d93fd72
Show file tree
Hide file tree
Showing 17 changed files with 828 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: create "Cross Selling" element
issue: NEXT-12059
flag: FEATURE_NEXT_10078
---
# Administration
* Added `sw-cms-el-cross-selling` component
* Added `sw-cms-el-config-cross-selling` component
* Added `sw-cms-el-preview-cross-selling` component
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ Shopware.Service('privileges')
'property_group:read',
'property_group_option:read',
'product_media:read',
'delivery_time:read'
'delivery_time:read',
'product_cross_selling:read',
'product_cross_selling_assigned_products:read'
],
dependencies: []
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import template from './sw-cms-el-cross-selling.html.twig';
import './sw-cms-el-cross-selling.scss';

const { Component, Mixin } = Shopware;
const { isEmpty } = Shopware.Utils.types;

Component.register('sw-cms-el-cross-selling', {
template,

mixins: [
Mixin.getByName('cms-element'),
Mixin.getByName('placeholder')
],

data() {
return {
sliderBoxLimit: 3
};
},

computed: {
demoProductElement() {
return {
config: {
boxLayout: {
source: 'static',
value: this.element.config.boxLayout.value
},
displayMode: {
source: 'static',
value: this.element.config.displayMode.value
},
elMinWidth: {
source: 'static',
value: this.element.config.elMinWidth.value
}
},
data: {
product: {
name: 'Lorem ipsum dolor',
description: `Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,
sed diam voluptua.`.trim(),
price: [
{ gross: 19.90 }
],
cover: {
media: {
url: '/administration/static/img/cms/preview_glasses_large.jpg',
alt: 'Lorem Ipsum dolor'
}
}
}
}
};
},

sliderBoxMinWidth() {
if (this.element.config.elMinWidth.value && this.element.config.elMinWidth.value.indexOf('px') > -1) {
return `repeat(auto-fit, minmax(${this.element.config.elMinWidth.value}, 1fr))`;
}

return null;
},

currentDeviceView() {
return this.cmsPageState.currentCmsDeviceView;
},

crossSelling() {
if (!this.element.data.product || !this.element.data.product.crossSellings.length) {
return {
name: 'Similar products'
};
}

return this.element.data.product.crossSellings[0];
},

crossSellingProducts() {
return (this.element.data.product.crossSellings.length)
? this.element.data.product.crossSellings[0].assignedProducts
: [];
},

currentDemoEntity() {
if (this.cmsPageState.currentMappingEntity === 'product') {
return this.cmsPageState.currentDemoEntity;
}

return null;
}
},

watch: {
'element.config.elMinWidth.value': {
handler() {
this.setSliderRowLimit();
}
},

currentDeviceView() {
setTimeout(() => {
this.setSliderRowLimit();
}, 400);
}
},

created() {
this.createdComponent();
},

mounted() {
this.mountedComponent();
},

methods: {
createdComponent() {
this.initElementConfig('cross-selling');
this.initElementData('cross-selling');
},

mountedComponent() {
this.setSliderRowLimit();
},

setSliderRowLimit() {
if (isEmpty(this.element.config)) {
this.createdComponent();
}

if (this.currentDeviceView === 'mobile' || (this.$refs.productHolder && this.$refs.productHolder.offsetWidth < 500)) {
this.sliderBoxLimit = 1;
return;
}

if (!this.element.config.elMinWidth.value ||
this.element.config.elMinWidth.value === 'px' ||
this.element.config.elMinWidth.value.indexOf('px') === -1) {
this.sliderBoxLimit = 3;
return;
}

if (parseInt(this.element.config.elMinWidth.value.replace('px', ''), 10) <= 0) {
return;
}

// Subtract to fake look in storefront which has more width
const fakeLookWidth = 100;
const boxWidth = this.$refs.productHolder.offsetWidth;
const elGap = 32;
let elWidth = parseInt(this.element.config.elMinWidth.value.replace('px', ''), 10);

if (elWidth >= 300) {
elWidth -= fakeLookWidth;
}

this.sliderBoxLimit = Math.floor(boxWidth / (elWidth + elGap));
},

getProductEl(product) {
return {
config: {
boxLayout: {
source: 'static',
value: this.element.config.boxLayout.value
},
displayMode: {
source: 'static',
value: this.element.config.displayMode.value
}
},
data: {
product
}
};
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{% block sw_cms_element_cross_selling %}
<div class="sw-cms-el-cross-selling">
{% block sw_cms_element_cross_selling_title %}
<h2 class="sw-cms-el-cross-selling__title">
{{ placeholder(crossSelling, 'name', crossSelling.name) }}
</h2>
{% endblock %}

{% block sw_cms_element_cross_selling_content %}
<div class="sw-cms-el-cross-selling__content">
{% block sw_cms_element_cross_selling_arrow_left %}
<div class="sw-cms-el-cross-selling__navigation">
<sw-icon class="sw-cms-el-cross-selling__navigation-button"
name="default-arrow-head-left"
size="24">
</sw-icon>
</div>
{% endblock %}

{% block sw_cms_element_cross_selling_product_holder %}
<div class="sw-cms-el-cross-selling__product-holder" :style="sliderBoxMinWidth" ref="productHolder">
<template v-if="!element.data.product || !element.data.product.crossSellings.length">
{% block sw_cms_element_cross_selling_demo_data %}
<sw-cms-el-product-box v-for="index in sliderBoxLimit"
:element="demoProductElement"
:key="index">
</sw-cms-el-product-box>
{% endblock %}
</template>

<template v-else>
{% block sw_cms_element_cross_selling_products %}
<sw-cms-el-product-box v-for="(crossSelling, index) in crossSellingProducts"
v-if="index < sliderBoxLimit"
:element="getProductEl(crossSelling.product)"
:key="crossSelling.id">
</sw-cms-el-product-box>
{% endblock %}
</template>
</div>
{% endblock %}

{% block sw_cms_element_cross_selling_arrow_right %}
<div class="sw-cms-el-cross-selling__navigation">
<sw-icon class="sw-cms-el-cross-selling__navigation-button"
name="default-arrow-head-right"
size="24">
</sw-icon>
</div>
{% endblock %}
</div>
{% endblock %}
</div>
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@import "~scss/variables";

.sw-cms-el-cross-selling {
width: 100%;

&__title {
display: inline-block;
margin-bottom: 20px;
padding-bottom: 5px;
border-bottom: 1px solid $color-gray-900;
font-size: 14px;
}

&__content {
display: grid;
grid-template-columns: 32px 1fr 32px;
gap: 0 8px;
}

&__navigation {
align-self: center;
justify-self: center;
}

&__product-holder {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(185px, 1fr));
gap: 0 32px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import template from './sw-cms-el-config-cross-selling.html.twig';
import './sw-cms-el-config-cross-selling.scss';

const { Component, Mixin } = Shopware;
const { Criteria } = Shopware.Data;

Component.register('sw-cms-el-config-cross-selling', {
template,

inject: ['repositoryFactory'],

mixins: [
Mixin.getByName('cms-element')
],

computed: {
productRepository() {
return this.repositoryFactory.create('product');
},

productSelectContext() {
return {
...Shopware.Context.api,
inheritance: true
};
},

productCriteria() {
const criteria = new Criteria();
criteria.addAssociation('options.group');

return criteria;
},

selectedProductCriteria() {
const criteria = new Criteria();
criteria.addAssociation('crossSellings.assignedProducts.product');

return criteria;
},

isProductPageType() {
return this.cmsPageState.currentPage.type === 'product_detail';
}
},

created() {
this.createdComponent();
},

methods: {
createdComponent() {
this.initElementConfig('cross-selling');
},

onProductChange(productId) {
if (!productId) {
this.element.config.product.value = null;
this.$set(this.element.data, 'product', null);
} else {
this.productRepository.get(productId, this.productSelectContext, this.selectedProductCriteria).then((product) => {
this.element.config.product.value = productId;
this.$set(this.element.data, 'product', product);
});
}

this.$emit('element-update', this.element);
}
}
});
Loading

0 comments on commit d93fd72

Please sign in to comment.