HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
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>