diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c1d6470b..e0813bb69a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.11.0-rc.2] - unreleased ### Fixed +- Fixed deprecated getter in cmsBlock store - @resubaka (#3683) +- Fixed problem around dynamic urls when default storeView is set with appendStoreCode false and url set to / . @resubaka (#3685) +- Fixed three problems you can run into when you have bundle products - @resubaka (#3692) - Reset nested menu after logout - @gibkigonzo (#3680) - - - Fixed deprecated getter in cmsBlock store - @resubaka (#3683) - - Fixed problem around dynamic urls when default storeView is set with appendStoreCode false and url set to / . @resubaka (#3685) - - Fixed three problems you can run into when you have bundle products - @resubaka (#3692) +- Fixed handling checkbox custom option (#2781) ## [1.11.0-rc.1] - 2019.10.03 diff --git a/core/modules/catalog/components/ProductCustomOptions.ts b/core/modules/catalog/components/ProductCustomOptions.ts index 34f80a5da6..821eecde03 100644 --- a/core/modules/catalog/components/ProductCustomOptions.ts +++ b/core/modules/catalog/components/ProductCustomOptions.ts @@ -1,21 +1,10 @@ +import { customOptionFieldName, selectedCustomOptionValue, defaultCustomOptionValue } from '@vue-storefront/core/modules/catalog/helpers/customOption'; import { mapMutations } from 'vuex' import * as types from '../store/product/mutation-types' import rootStore from '@vue-storefront/core/store' import i18n from '@vue-storefront/i18n' import { Logger } from '@vue-storefront/core/lib/logger' -function _defaultOptionValue (co) { - switch (co.type) { - case 'radio': return co.values && co.values.length ? co.values[0].option_type_id : 0 - case 'checkbox': return false - default: return '' - } -} - -function _fieldName (co) { - return 'customOption_' + co.option_id -} - export const ProductCustomOptions = { name: 'ProductCustomOptions', props: { @@ -26,21 +15,34 @@ export const ProductCustomOptions = { }, data () { return { - inputValues: { - }, - selectedOptions: { - }, + inputValues: {}, validation: { rules: {}, results: {} } } }, + computed: { + selectedOptions () { + const customOptions = this.product.custom_options + if (!customOptions) { + return {} + } + + return customOptions.reduce((selectedOptions, option) => { + const fieldName = customOptionFieldName(option) + selectedOptions[fieldName] = selectedCustomOptionValue(option.type, option.values, this.inputValues[fieldName]) + return selectedOptions + }, {}) + } + }, created () { rootStore.dispatch('product/addCustomOptionValidator', { validationRule: 'required', // You may add your own custom fields validators elsewhere in the theme validatorFunction: (value) => { - return { error: (value === null || value === '') || (value === false) || (value === 0), message: i18n.t('Field is required.') } + const error = Array.isArray(value) ? !value.length : !value + const message = i18n.t('Field is required.') + return { error, message } } }) this.setupInputFields() @@ -50,26 +52,24 @@ export const ProductCustomOptions = { setCustomOptionValue: types.PRODUCT_SET_CUSTOM_OPTION // map `this.add()` to `this.$store.commit('increment')` }), setupInputFields () { - for (let co of this.product.custom_options) { - const fieldName = _fieldName(co) - this['inputValues'][fieldName] = _defaultOptionValue(co) - if (co.is_require) { // validation rules are very basic + for (const customOption of this.product.custom_options) { + const fieldName = customOptionFieldName(customOption) + this['inputValues'][fieldName] = defaultCustomOptionValue(customOption) + if (customOption.is_require) { // validation rules are very basic this.validation.rules[fieldName] = 'required' // TODO: add custom validators for the custom options } - this.optionChanged(co, co.values && co.values.length > 0 ? co.values[0] : null) + this.optionChanged(customOption) } }, - optionChanged (option, opval = null) { - const fieldName = _fieldName(option) - const value = opval === null ? this.inputValues[fieldName] : opval.option_type_id + optionChanged (option) { + const fieldName = customOptionFieldName(option) this.validateField(option) - this.setCustomOptionValue({ optionId: option.option_id, optionValue: value }) + this.setCustomOptionValue({ optionId: option.option_id, optionValue: this.selectedOptions[fieldName] }) this.$store.dispatch('product/setCustomOptions', { product: this.product, customOptions: this.$store.state.product.current_custom_options }) // TODO: move it to "AddToCart" - this.selectedOptions[fieldName] = (opval === null ? value : opval) this.$bus.$emit('product-after-customoptions', { product: this.product, option: option, optionValues: this.selectedOptions }) }, validateField (option) { - const fieldName = _fieldName(option) + const fieldName = customOptionFieldName(option) const validationRule = this.validation.rules[fieldName] this.product.errors.custom_options = null if (validationRule) { diff --git a/core/modules/catalog/helpers/customOption.ts b/core/modules/catalog/helpers/customOption.ts new file mode 100644 index 0000000000..dbd31387fc --- /dev/null +++ b/core/modules/catalog/helpers/customOption.ts @@ -0,0 +1,44 @@ +import { CustomOption, OptionValue, InputValue } from './../types/CustomOption'; + +export const defaultCustomOptionValue = (customOption: CustomOption): InputValue => { + switch (customOption.type) { + case 'radio': { + return customOption.values && customOption.values.length ? customOption.values[0].option_type_id : 0 + } + case 'checkbox': { + return [] + } + default: { + return '' + } + } +} + +export const customOptionFieldName = (customOption: CustomOption): string => { + return 'customOption_' + customOption.option_id +} + +export const selectedCustomOptionValue = (optionType: string, optionValues: OptionValue[] = [], inputValue: InputValue): string => { + switch (optionType) { + case 'field': { + return inputValue as string + } + case 'radio': + case 'select': + case 'drop_down': { + const selectedValue = optionValues.find((value) => value.option_type_id === inputValue as number) + + return String(selectedValue && selectedValue.option_type_id) || '' + } + case 'checkbox': { + const checkboxOptionValues = inputValue as number[] || [] + + return optionValues.filter((value) => checkboxOptionValues.includes(value.option_type_id)) + .map((value) => value.option_type_id) + .join(',') + } + default: { + return '' + } + } +} diff --git a/core/modules/catalog/types/CustomOption.ts b/core/modules/catalog/types/CustomOption.ts new file mode 100644 index 0000000000..d9c6c9a399 --- /dev/null +++ b/core/modules/catalog/types/CustomOption.ts @@ -0,0 +1,24 @@ +export interface CustomOption { + image_size_x: number, + image_size_y: number, + is_require: boolean, + max_characters: number, + option_id: number, + product_sku: string, + sort_order: number, + title: string, + type: string, + price?: number, + price_type?: string, + values?: OptionValue[] +} + +export interface OptionValue { + option_type_id: number, + price: number, + price_type: string, + sort_order: number, + title: string +} + +export type InputValue = string | number | number[] diff --git a/src/themes/default/components/core/ProductCustomOptions.vue b/src/themes/default/components/core/ProductCustomOptions.vue index 5bf74143f7..7a59caeb31 100644 --- a/src/themes/default/components/core/ProductCustomOptions.vue +++ b/src/themes/default/components/core/ProductCustomOptions.vue @@ -18,7 +18,7 @@ >