File: D:/HostingSpaces/SBogers10/shop.komma.nl/resources/js/components/vue/product/ProductGroup.vue
<template>
<div class="o-product">
<div class="o-product__figure">
<img class="o-product__image" :src="this.selectedProduct ? this.selectedProduct.imageUrl : ''"
data-test="productable-image"/>
</div>
<div class="o-product__content">
<h2 class="o-product__name">{{ this.selectedProduct ? this.selectedProduct.displayName : '' }}</h2>
<div class="o-product__price" data-test="price-inc">{{
this.selectedProduct ? this.selectedProduct.currencySymbol + this.selectedProduct.priceIncFormatted : ''
}}
</div>
<div class="o-product__subprice" data-test="price-ex">{{
this.selectedProduct ? this.selectedProduct.currencySymbol + this.selectedProduct.priceExFormatted : ''
}} <span>{{ trans('productable').ex_vat }}</span>
</div>
<div class="o-product__properties to-style" v-if="this.productGroup && this.productGroup.filterPropertyKeys && this.productGroup.displayAs === 2">
<label v-for="filterPropertyKey in this.productGroup.filterPropertyKeys">
{{ getTranslation(filterPropertyKey).name }}:
<select @change="changedProperty" v-model="selectedValues[filterPropertyKey.id]" :ref="'propertySelect' + filterPropertyKey.id" :name="'propertySelect' + filterPropertyKey.id">
<option v-for="(propertyData, productId) in filterPropertiesByProductId(filterPropertyKey)"
:value="propertyData">
{{ propertyData }}
</option>
</select>
<br>
</label>
</div>
<div class="o-product__properties to-style" v-if="this.productGroup && this.productGroup.display_as === 1">
<label>
{{ trans('productable').choose_product }}:
<select @change="selectProductById" :value="this.selectedProduct.id">
<option v-for="(product) in productGroup.products" :value="product.id">
{{ getTranslation(product).name }}
</option>
</select>
<br>
</label>
</div>
<product-actions v-if="selectedProduct" :productable-enum="1" :productable-id="this.selectedProduct.id"
@itemQuantityChanged="itemQuantityChanged"/>
</div>
<table class="c-specification-table u-spacing-mt1" v-if="selectedProduct">
<tr v-for="property in selectedProduct.properties"
v-if="property.values.length > 0 && getTranslation(property.values[0]) && getTranslation(property.key)">
<td>{{ getTranslation(property.key).name }}</td>
<td>{{ getTranslation(property.values[0]).name }}</td>
</tr>
</table>
</div>
</template>
<script>
import {mapGetters} from "vuex";
import {CheckoutService} from "../../../services/checkoutService"
import {ShoppingCartService} from '../../../services/shoppingCartService'
import {ProductableService} from "../../../services/productableService"
import ProductActions from './productActions'
import Vue from 'vue'
export default {
props: {
productGroupId: {type: Number, required: true},
selectedProductId: {type: Number, required: true},
},
components: {
'product-actions': ProductActions
},
data: function () {
/**
* selectedValuesByPropertyKeyId will be an object that looks like this:
*
*/
return {
productGroup: null,
selectedProduct: null,
quantity: 1,
selectedValuesByPropertyKeyId: null
}
},
methods: {
/**
* Triggered when the quantity input was changed.
*
* @param {Number} itemId The itemId of the quantity input.
* @param {Number} amount The current amount of the quantity input
*/
itemQuantityChanged: function (itemId, amount) {
this.quantity = amount;
},
/**
* Triggered when the add to cart button was clicked.
*
* @param {MouseEvent} event
*/
addToCartClicked: function (event) {
this.shoppingCartService.addProductToShoppingcart(this.internalProductableId, this.selectedProduct, this.quantity).then((response) => {
this.checkoutService.getCheckoutInformation().then((response) => {
const checkoutInformationData = response.data;
window.location.href = checkoutInformationData.cartRoute;
});
}).catch((error) => {
console.error('productAction: ', error)
})
},
filterPropertiesByProductId: function (filterPropertyKey) {
const propertiesByProduct = {}
let length = this.productGroup.products.length;
for (let index = 0; index < length; index++) {
//Loop over the product groups properties
let currentProductGroupProduct = this.productGroup.products[index];
//Get the products property that has the same key as the given filter property key
let currentProductGroupProductProperty = currentProductGroupProduct.properties.find(
(property) => property.key.id === filterPropertyKey.id
);
if (!currentProductGroupProductProperty) continue;
if (currentProductGroupProductProperty.values.length === 0) continue;
const valueTranslation = this.getTranslation(currentProductGroupProductProperty.values[0]) ? this.getTranslation(currentProductGroupProductProperty.values[0]).name : '-';
//Check if the value translation already is in the propertiesByProduct object. We dont add it a second time
if (Object.values(propertiesByProduct).find((property) => property === valueTranslation)) continue;
propertiesByProduct[currentProductGroupProduct.id] = valueTranslation
}
return propertiesByProduct;
},
enableOrDisableFilterPropertySelectOptions() {
if(this.productGroup.display_as !== 2) return //Number 2 is variant select. Only then code below works. Also see the php file ProductGroupDisplayEnum.
let filterPropertyKeysCount = this.productGroup.filterPropertyKeys.length;
//Loop over all property selects
for(let index = 0; index < filterPropertyKeysCount; index++) {
const filterPropertyKey = this.productGroup.filterPropertyKeys[index];
const select = this.$refs['propertySelect'+ (filterPropertyKey.id)][0]
let options = select.children;
//First enable the property select options
for(let optionIndex = 0; optionIndex < options.length; optionIndex++) {
const currentOption = options[optionIndex];
currentOption.disabled = false;
}
//Get the other values (not from the current select)
const otherSelectedValuesByPropertyKeyId = this.selectedValuesByPropertyKeyIdExceptPropertyKey(filterPropertyKey);
const otherValuesInSelect = this.otherValuesInSelect(select, this.selectedValuesByPropertyKeyId[filterPropertyKey.id]);
//Disable some of the select options
let otherValuesFromSelectCount = otherValuesInSelect.length;
for(let index = 0; index < otherValuesFromSelectCount; index++) {
const currentOtherValueFromSelect = otherValuesInSelect[index];
const currentFilterPropertyObject = {};
currentFilterPropertyObject[filterPropertyKey.id] = currentOtherValueFromSelect;
let propertySet = Object.assign(otherSelectedValuesByPropertyKeyId, currentFilterPropertyObject);
const products = this.findProductsHavingFilterPropertyKeysWithValues(propertySet);
if(products.length === 0) {
this.disableOptionsUsingPropertySet(currentFilterPropertyObject);
}
}
}
},
disableOptionsUsingPropertySet(filterPropertyKeySet) {
for(let propertyKeyId in filterPropertyKeySet) {
if(!Object.hasOwnProperty.call(filterPropertyKeySet, propertyKeyId)) continue;
const propertyValue = filterPropertyKeySet[propertyKeyId];
const select = this.$refs['propertySelect'+propertyKeyId][0];
if(!select) continue;
Array.from(select.children).forEach((option) => {
if(option.value === propertyValue) option.disabled = true;
})
}
},
findProductsHavingFilterPropertyKeysWithValues(filterPropertyKeySet) {
let products = this.productGroup.products;
for(let filterPropertyKeyId in filterPropertyKeySet) {
if(!Object.prototype.hasOwnProperty.call(filterPropertyKeySet, filterPropertyKeyId)) continue;
const filterPropertyValue = filterPropertyKeySet[filterPropertyKeyId];
//Get the product that has a property with the same filterPropertyKeyId
products = products.filter((product) => {
return product.properties.find(property => {
const propertyFound = property.key.id === parseInt(filterPropertyKeyId)
const valueFound = property.values.find((value) => {
const translation = this.getTranslation(value);
return translation.name === filterPropertyValue;
})
return propertyFound && valueFound //Combination found or not. This determines if it will be in the results.
})
})
}
return products;
},
otherValuesInSelect(select, currentValue) {
return Array.from(select.children).reduce((values, option) => {
if(option.value !== currentValue) values.push(option.value)
return values;
}, [])
},
selectedValuesByPropertyKeyIdExceptPropertyKey(filterPropertyKey) {
const toReturn = {}
for(const filterPropertyKeyId in this.selectedValuesByPropertyKeyId) {
if(!Object.prototype.hasOwnProperty.call(this.selectedValuesByPropertyKeyId, filterPropertyKeyId) || filterPropertyKey.id === parseInt(filterPropertyKeyId, 10)) continue;
toReturn[filterPropertyKeyId] = this.selectedValuesByPropertyKeyId[filterPropertyKeyId];
}
return toReturn;
},
getTranslation(translatable) {
if (!translatable) return;
if (!translatable.hasOwnProperty('translations')) throw new Error('No valid translatable was given. It must have a translations property. But did not have it.')
const store = this.$store;
if (!this.$store.state.sites.currentSiteLanguage) return;
return translatable.translations.find((translation) => {
if (!translation.hasOwnProperty('language')) throw new Error('The translation did not have a language property while it should.')
return translation.language.iso_2 === store.state.sites.currentSiteLanguage.iso_2
})
},
changedProperty(filterPropertyKey) {
this.enableOrDisableFilterPropertySelectOptions();
//Get the product ids that are present in all selectedValuesByPropertyKeyId arrays
const foundProductIds = this.productIdsHavingAllSelectedPropertyValues();
if (foundProductIds.length > 0) {
this.selectedProduct = this.productGroup.products.find((product) => product.id === foundProductIds[0]);
} else {
console.warn('No product found using the given property combinations. Not changing the product');
}
},
productIdsHavingAllSelectedPropertyValues() {
let foundProductIds = [];
const arrays = Object.values(this.productIdsPerSelectedPropertyKey())
if (arrays.length > 0) {
//sort the arrays by length first. Where the longest array is at position 0
arrays.sort((a, b) => {
if (a.length < b.length) return 1
else if (a.length > b.length) return -1
else return 0
})
//Get the first array (which is the longest). Check if its values exist in all arrays. If so, store them in foundProductIds
foundProductIds = arrays[0].filter((valueToFindInAllArrays) => {
return arrays.every((array) => array.indexOf(valueToFindInAllArrays) !== -1)
})
}
return foundProductIds;
},
/**
* Build an object in which the keys are the filter property keys. And the values are arrays, containing product ids of the product groups products that have that property key.
* For example. Consider this object: { 2: [3,6] }. This means that products with id 3 and 6 will have a property that has a key with id 2.
* @return {{}}
*/
productIdsPerSelectedPropertyKey() {
let productIdsPerPropertyKeyId = {};
for (const propertyKeyId in this.selectedValuesByPropertyKeyId) {
if(!Object.prototype.hasOwnProperty.call(this.selectedValuesByPropertyKeyId, propertyKeyId)) continue;
productIdsPerPropertyKeyId[propertyKeyId] = this.productGroup.products.filter(product => {
const foundProperty = product.properties.find(property => {
return parseInt(propertyKeyId, 10) === property.key.id
})
if (!foundProperty) return false;
const propertyValues = foundProperty.values.map((value) => {
const translation = this.getTranslation(value);
return (translation) ? translation.name : null;
})
return Object.values(propertyValues).includes(this.selectedValuesByPropertyKeyId[propertyKeyId]);
}).map(product => product.id);
}
return productIdsPerPropertyKeyId;
},
updateSelectedValuesUsingSelectedProduct() {
const length = this.productGroup.filterPropertyKeys.length;
for(let index = 0; index < length; index++) {
const filterPropertyKey = this.productGroup.filterPropertyKeys[index];
let selectedProductProperty = this.selectedProduct.properties.find(
(property) => {
return property.key.id === filterPropertyKey.id
}
);
let value = null
if(selectedProductProperty && selectedProductProperty.values.length > 0) {
value = this.getTranslation(selectedProductProperty.values[0]).name;
}
if(!this.selectedValuesByPropertyKeyId) this.selectedValuesByPropertyKeyId = {};
Vue.set(this.selectedValuesByPropertyKeyId, filterPropertyKey.id, value)
}
},
selectProductById(event) {
const productId = parseInt(event.target.value);
const product = this.productGroup.products.find((product) => product.id === productId);
if(product) this.selectedProduct = product;
}
},
computed: {
...mapGetters({
//Maps the getter function "translation" in vuex module translations.js to a computed property called trans in this component
trans: "g11n/translation/translation",
}),
selectedValues: {
get: function () {
if(!this.selectedValuesByPropertyKeyId) this.updateSelectedValuesUsingSelectedProduct();
return this.selectedValuesByPropertyKeyId;
},
set: function (newValue) {
this.selectedValuesByPropertyKeyId = newValue
}
},
},
watch: {
selectedProduct: function(newSelectedProduct, oldSelectedProduct) {
this.$nextTick(this.enableOrDisableFilterPropertySelectOptions)
}
},
created() {
this.shoppingCartService = new ShoppingCartService();
this.checkoutService = new CheckoutService();
this.productableService = new ProductableService();
},
mounted() {
this.productableService.productable(this.$props.productGroupId, 2).then((response) => {
if (response.status !== 200) return;
this.productGroup = response.data;
if (this.productGroup.products.length > 0) {
if (this.$props.selectedProductId) {
this.selectedProduct = this.productGroup.products.find((product) => product.id === this.$props.selectedProductId);
} else {
this.selectedProduct = this.productGroup.products[0];
}
}
})
}
}
</script>