0

i want multiple text inputs that when i change one of them i want others to change with the same value too . my inputs are generating in a loop of v-for like below :

  <tbody>
<variant-item v-for='(variant, index) in variants' :variant="variant" :key="index" :index="index" "></variant-item>
 </tbody>

and here the input is getting generated :

<td>
   <div class="control-group" :class="[errors.has(variantInputName + '[price]') ? 'has-error' : '']">
   <input type="number" v-model="variant.price" :name="[variantInputName + '[price]']" />
   <span class="control-error" v-if="errors.has(variantInputName + '[price]')">@{{ errors.first(variantInputName + '[price]') }}</span></div>
</td>

so with this code if i have 2 products for example the result would be like below :

<td><div class="control-group"><input type="number" name="variants[4344][price]" data-vv-as="&quot;price&quot;" step="any" class="control"> <!----></div></td>

.
.
.
.
<td><div class="control-group"><input type="number" name="variants[4345][price]" data-vv-as="&quot;[price&quot;" > <!----></div></td>

now I want this 2 or multiple inputs change together .

@section('css')
    @parent
    <style>
        .table th.price, .table th.weight {
            width: 100px;
        }
        .table th.actions {
            width: 85px;
        }
        .table td.actions .icon {
            margin-top: 8px;
        }
        .table td.actions .icon.pencil-lg-icon {
            margin-right: 10px;
        }
    </style>
@stop

{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.variations.before', ['product' => $product]) !!}

<accordian :title="'{{ __('admin::app.catalog.products.variations') }}'" :active="true">
    <div slot="body">

        {!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.variations.controls.before', ['product' => $product]) !!}

        <button type="button" class="btn btn-md btn-primary" @click="showModal('addVariant')">
            {{ __('admin::app.catalog.products.add-variant-btn-title') }}
        </button>

        <variant-list></variant-list>

        {!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.variations.controls.after', ['product' => $product]) !!}

    </div>
</accordian>

{!! view_render_event('bagisto.admin.catalog.product.edit_form_accordian.variations.after', ['product' => $product]) !!}

<modal id="addVariant" :is-open="modalIds.addVariant">
    <h3 slot="header">{{ __('admin::app.catalog.products.add-variant-title') }}</h3>

    <div slot="body">
        <variant-form></variant-form>
    </div>
</modal>

@push('scripts')
    @parent

    <script type="text/x-template" id="variant-form-template">
        <form method="POST" action="{{ route('admin.catalog.products.store') }}" data-vv-scope="add-variant-form" @submit.prevent="addVariant('add-variant-form')">

            <div class="page-content">
                <div class="form-container">

                    <div v-for='(attribute, index) in super_attributes' class="control-group" :class="[errors.has('add-variant-form.' + attribute.code) ? 'has-error' : '']">
                        <label :for="attribute.code" class="required">@{{ attribute.admin_name }}</label>
                        <select v-validate="'required'" v-model="variant[attribute.code]" class="control" :id="attribute.code" :name="attribute.code" :data-vv-as="'&quot;' + attribute.admin_name + '&quot;'">
                            <option v-for='(option, index) in attribute.options' :value="option.id">@{{ option.admin_name }}</option>
                        </select>
                        <span class="control-error" v-if="errors.has('add-variant-form.' + attribute.code)">@{{ errors.first('add-variant-form.' + attribute.code) }}</span>
                    </div>

                    <button type="submit" class="btn btn-lg btn-primary">
                        {{ __('admin::app.catalog.products.add-variant-title') }}
                    </button>

                </div>
            </div>

        </form>
    </script>

    <script type="text/x-template" id="variant-list-template">
        <div class="table" style="margin-top: 20px; overflow-x: auto;">
            <table>

                <thead>
                    <tr>
                        <th class="sku">{{ __('admin::app.catalog.products.sku') }}</th>
                        <th>{{ __('admin::app.catalog.products.name') }}</th>

                        @foreach ($product->super_attributes as $attribute)
                            <th class="{{ $attribute->code }}" style="width: 150px">{{ $attribute->admin_name }}</th>
                        @endforeach

                        <th class="qty">{{ __('admin::app.catalog.products.qty') }}</th>
                        <th class="price">{{ __('admin::app.catalog.products.price') }}</th>
                        <th class="weight">{{ __('admin::app.catalog.products.weight') }}</th>
                        <th class="status">{{ __('admin::app.catalog.products.status') }}</th>
                        <th class="actions"></th>
                    </tr>
                </thead>

                <tbody>

                    <variant-item v-for='(variant, index) in variants' :variant="variant" :key="index" :index="index" :variant-price.sync="variantPrice" @onRemoveVariant="removeVariant($event)"></variant-item>

                </tbody>

            </table>
        </div>
    </script>

    <script type="text/x-template" id="variant-item-template">
        <tr>
            <td>
                <div class="control-group" :class="[errors.has(variantInputName + '[sku]') ? 'has-error' : '']">
                    <input type="text" v-validate="'required'" v-model="variant.sku" :name="[variantInputName + '[sku]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.sku') }}&quot;" v-slugify/>
                    <span class="control-error" v-if="errors.has(variantInputName + '[sku]')">@{{ errors.first(variantInputName + '[sku]') }}</span>
                </div>
            </td>

            <td>
                <div class="control-group" :class="[errors.has(variantInputName + '[name]') ? 'has-error' : '']">
                    <input type="text" v-validate="'required'" v-model="variant.name"  :name="[variantInputName + '[name]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.name') }}&quot;"/>
                    <span class="control-error" v-if="errors.has(variantInputName + '[name]')">@{{ errors.first(variantInputName + '[name]') }}</span>
                </div>
            </td>

            <td v-for='(attribute, index) in superAttributes'>
                <div class="control-group">
                    <input type="hidden" :name="[variantInputName + '[' + attribute.code + ']']" :value="variant[attribute.code]"/>
                    <input type="text" class="control" :value="optionName(variant[attribute.code])" readonly/>
                </div>
            </td>

            <td>
                <button style="width: 100%;" type="button" class="dropdown-btn dropdown-toggle">
                    @{{ totalQty }}
                    <i class="icon arrow-down-icon"></i>
                </button>

                <div class="dropdown-list">
                    <div class="dropdown-container">
                        <ul>
                            <li v-for='(inventorySource, index) in inventorySources'>
                                <div class="control-group" :class="[errors.has(variantInputName + '[inventories][' + inventorySource.id + ']') ? 'has-error' : '']">
                                    <label>@{{ inventorySource.name }}</label>
                                    <input type="text" v-validate="'numeric|min:0'" :name="[variantInputName + '[inventories][' + inventorySource.id + ']']" v-model="inventories[inventorySource.id]" class="control" v-on:keyup="updateTotalQty()" :data-vv-as="'&quot;' + inventorySource.name  + '&quot;'"/>
                                    <span class="control-error" v-if="errors.has(variantInputName + '[inventories][' + inventorySource.id + ']')">@{{ errors.first(variantInputName + '[inventories][' + inventorySource.id + ']') }}</span>
                                </div>
                            </li>
                        </ul>
                    </div>
                </div>
            </td>

            <td>
                <div class="control-group" :class="[errors.has(variantInputName + '[price]') ? 'has-error' : '']">
                    <input type="number" v-validate="'required|min_value:0.0001'" v-model="variant.price" :name="[variantInputName + '[price]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.price') }}&quot;" step="any"/>
                    <span class="control-error" v-if="errors.has(variantInputName + '[price]')">@{{ errors.first(variantInputName + '[price]') }}</span>
                </div>
            </td>

            <td>
                <div class="control-group" :class="[errors.has(variantInputName + '[weight]') ? 'has-error' : '']">
                    <input type="number" v-validate="'required|min_value:0.0001'" v-model="variant.weight"  :name="[variantInputName + '[weight]']" class="control" data-vv-as="&quot;{{ __('admin::app.catalog.products.weight') }}&quot;" step="any"/>
                    <span class="control-error" v-if="errors.has(variantInputName + '[weight]')">@{{ errors.first(variantInputName + '[weight]') }}</span>
                </div>
            </td>

            <td>
                <div class="control-group">
                    <select type="text" v-model="variant.status" :name="[variantInputName + '[status]']" class="control">
                        <option value="1" :selected="variant.status">{{ __('admin::app.catalog.products.enabled') }}</option>
                        <option value="0" :selected="!variant.status">{{ __('admin::app.catalog.products.disabled') }}</option>
                    </select>
                </div>
            </td>

            <td class="actions">
                <a :href="['{{ route('admin.catalog.products.index') }}/edit/' + variant.id]"><i class="icon pencil-lg-icon"></i></a>
                <i class="icon remove-icon" @click="removeVariant()"></i>
            </td>
        </tr>
    </script>

    <script>
        $(document).ready(function () {
            Vue.config.ignoredElements = [
                'variant-form',
                'variant-list',
                'variant-item'
            ];
        });

        var super_attributes = @json(app('\Webkul\Product\Repositories\ProductRepository')->getSuperAttributes($product));
        var variants = @json($product->variants);

        Vue.component('variant-form', {

            data: function() {
                return {
                    variant: {},
                    super_attributes: super_attributes
                }
            },

            template: '#variant-form-template',

            created: function () {
                this.resetModel();
            },

            methods: {
                addVariant: function (formScope) {
                    this.$validator.validateAll(formScope).then((result) => {
                        if (result) {
                            var this_this = this;

                            var filteredVariants = variants.filter(function(variant) {
                                var matchCount = 0;

                                for (var key in this_this.variant) {
                                    if (variant[key] == this_this.variant[key]) {
                                        matchCount++;
                                    }
                                }

                                return matchCount == this_this.super_attributes.length;
                            })

                            if (filteredVariants.length) {
                                this.$parent.closeModal();

                                window.flashMessages = [{'type': 'alert-error', 'message': "{{ __('admin::app.catalog.products.variant-already-exist-message') }}" }];

                                this.$root.addFlashMessages()
                            } else {
                                var optionIds = [];
                                for (var key in this_this.variant) {
                                    optionIds.push(this_this.variant[key]);
                                }

                                variants.push(Object.assign({
                                        sku: '{{ $product->sku }}' + '-variant-' + optionIds.join('-'),
                                        name: '',
                                        price: 0,
                                        weight: 0,
                                        status: 1
                                    }, this.variant));

                                this.resetModel();

                                this.$parent.closeModal();
                            }
                        }
                    });
                },

                resetModel: function () {
                    var this_this = this;

                    this.super_attributes.forEach(function(attribute) {
                        this_this.variant[attribute.code] = '';
                    })
                }
            }
        });

        Vue.component('variant-list', {

            template: '#variant-list-template',

            inject: ['$validator'],

            data: function() {
                return {
                    variants: variants,
                    variantPrice:0,
                    old_variants: @json(old('variants')),

                    superAttributes: super_attributes
                }
            },

            created: function () {
                var index = 0;

                for (var key in this.old_variants) {
                    var variant = this.old_variants[key];

                    if (key.indexOf('variant_') !== -1) {
                        var inventories = [];

                        for (var inventorySourceId in variant['inventories']) {
                            inventories.push({'qty': variant['inventories'][inventorySourceId], 'inventory_source_id': inventorySourceId})
                        }

                        variant['inventories'] = inventories;

                        variants.push(variant);
                    } else {
                        for (var code in variant) {
                            if (code != 'inventories') {
                                variants[index][code] = variant[code];
                            } else {
                                variants[index][code] = [];

                                for (var inventorySourceId in variant[code]) {
                                    variants[index][code].push({'qty': variant[code][inventorySourceId], 'inventory_source_id': inventorySourceId})
                                }
                            }
                        }
                    }

                    index++;
                }
            },

            methods: {
                removeVariant: function(variant) {
                    let index = this.variants.indexOf(variant)

                    this.variants.splice(index, 1)
                },
            }

        });

        Vue.component('variant-item', {

            template: '#variant-item-template',

            props: ['variantPrice','index', 'variant'],

            inject: ['$validator'],

            data: function() {
                return {
                    inventorySources: @json($inventorySources),
                    inventories: {},
                    totalQty: 0,
                    superAttributes: super_attributes
                }
            },

            created: function () {
                var this_this = this;

                this.inventorySources.forEach(function(inventorySource) {
                    this_this.inventories[inventorySource.id] = this_this.sourceInventoryQty(inventorySource.id)
                    this_this.totalQty += parseInt(this_this.inventories[inventorySource.id]);
                })
            },

            computed: {
                variantInputName: function () {
                    if (this.variant.id)
                        return "variants[" + this.variant.id + "]";

                    return "variants[variant_" + this.index + "]";
                }
            },

            methods: {

                removeVariant: function () {
                    this.$emit('onRemoveVariant', this.variant)
                },
                onVariantPriceInput(event) {
                    this.$emit('update:variantPrice', Number(event.target.value));
                },
                optionName: function (optionId) {
                    var optionName = '';

                    this.superAttributes.forEach(function(attribute) {
                        attribute.options.forEach(function(option) {
                            if (optionId == option.id) {
                                optionName = option.admin_name;
                            }
                        });
                    })

                    return optionName;
                },

                sourceInventoryQty: function (inventorySourceId) {
                    if (! Array.isArray(this.variant.inventories))
                        return 0;

                    var inventories = this.variant.inventories.filter(function(inventory) {
                        return inventorySourceId === parseInt(inventory.inventory_source_id);
                    })

                    if (inventories.length)
                        return inventories[0]['qty'];

                    return 0;
                },

                updateTotalQty: function () {
                    this.totalQty = 0;

                    for (var key in this.inventories) {
                        this.totalQty += parseInt(this.inventories[key]);
                    }
                }
            }

        });
    </script>
@endpush
5
  • 1
    I think it would be helpful if you provided a simplified example of what you are trying to do. There is a lot of extra stuff here which may be necessary for the app, but is just clutter when it comes to this specific problem. If I wanted to create a prototype to reproduce your problem, I would need to remove all of the vee-validate cruft. Also, what is the variant-item custom component? I think you could reduce the example code to what is necessary to demonstrate the problem. This may even help you solve it on your own. Commented Oct 11, 2020 at 16:00
  • yes variant-item is my vue component and yes you are right i just guessed that this may help i will edit it Commented Oct 11, 2020 at 17:23
  • extra info removed Commented Oct 11, 2020 at 17:24
  • I don't understand if all variants are to share a single price or if each is to have its own price. Commented Oct 11, 2020 at 17:53
  • they all have to share the same price Commented Oct 11, 2020 at 20:19

1 Answer 1

1

If it is your objective to have a single price which is shared by all variants, then I would advise against having a price property per variant Object. variant.price suggests a price per variant. I would create a separate data property, say variantPrice that would be passed to each instance of the variant-item component.

(As an aside: If there is a single price that is to be shared by all variant-item components then it may be confusing to your users that you render a price input field per variant-item instead of having a single input field.)

One way to synchronize a value with multiple child components, and assuming you are using a version of Vue >= 2.3 and < 3, is to use the .sync modifier.

Your parent component - the one that references the variants Array - would have a new data property for variantPrice. Here is an example where the parent is the root Vue component:

new Vue({
    components: {
        variantItem: VariantItem
    },
    data() {
        return {
            variantPrice: 0,
            variants: [
                {
                    inputName: 'VariantOne'
                },
                {
                    inputName: 'VariantTwo'
                }
            ]
        };
    },
    el: "#App"
});

The template of the parent (root) would pass variantPrice as a prop to each variant-item instance using the .sync modifier which will bind the value of variantPrice to the update:variantPrice event emitted by the child. The template would look like:

<div id="App">
    <variant-item v-for="(variant, index) in variants" :key="index" :variant="variant" :variant-price.sync="variantPrice"></variant-item>
</div>

Next, we must ensure that our VariantItem component takes a variantPrice as a prop and emits the update:variantPrice event when the input field is modified by the user:

const VariantItem = Vue.component('variant-item', {
    methods: {
        onVariantPriceInput(event) {
            this.$emit('update:variantPrice', Number(event.target.value));
        }
    },
    props: {
        variant: {
            required: true,
            type: Object
        },
        variantPrice: {
            required: true,
            type: Number
        }
    },
    template: '#VariantItemTemplate'
});

The template for this component would become:

<div>
    <label :for="variant.inputName">{{ variant.inputName }}</label>
    <input :id="variant.inputName" :name="variant.inputName" :value="variantPrice" @input="onVariantPriceInput" type="number">
</div>

I have created a fiddle for your reference.

Sign up to request clarification or add additional context in comments.

3 Comments

hey thanks man thats a lot of help sorry i missed the answer to accept it
actually your answer is right and i saw that working in fiddle i changed that according to my code but this is not working there for the sake of the guide i added the whole file so you can maybe find and tell me where is the problem cause i really cant solve this part thanks in advance
You have added so much code that I do not know where to begin. I think you need to narrow-down your problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.