Emit in vue does not upgrade property in parent component - javascript

The emit works, I can see that in the vue developer tool. But the property in the parent element does not upgrade.
Child Component:
<template>
<div>
<ul>
<li v-for="(option, index) in options" :key="index" #click="selectOption(index)">
{{ option }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "Dropdown",
props: {
options: {
type: Array,
},
},
data() {
return {
value: "",
}
},
methods: {
selectOption(id) {
this.value = this.options[id];
this.$emit("clickedOption", this.value);
}
}
}
</script>
Parent Component:
<button v-on:clickedOption="selectRole($event)">Select Role</button>
methods: {
selectRole(value) {
this.role = value.payload;
},
It seems that the function selectRole is not getting executed.

I had to give the parent element the emit function like this:
<Dropdown :options="roleOptions" v-show="dropdownToggle" #clickedOption="selectRole($event)"></Dropdown>

Related

v-for with model vuejs

I need to execute a v-for, however I do not know how to add a v-model for each different component inside of v-for;
<template>
<ProfilePasswordField
v-for="(item, index) in profilePasswordItems"
:key="index"
:profilePasswordItem="item"
v-model="???"
>
</template>
This v-for will always be three items and I want to name the v-model's as: ['passaword', 'newPassword', confirmNewPassword']
How can I add those names dinamically for the v-model inside v-for?
I tried to do a list inside data() but it did not work. Something like that:
data (){
return{
listPassword: ['passaword', 'newPassword', 'confirmNewPassword']
}
},
methods: {
method1 () {
console.log(this.passaword)
console.log(this.newPassword)
console.log(this.confirmNewPassword)
}
}
The v-model directives cannot update the iteration variable itself therefore we should not use a linear array item in for-loop as the v-model variable.
There is another method you can try like this-
In the parent component-
<template>
<div>
<ProfilePasswordField
v-for="(item, index) in listPassword"
:key="index"
:profilePasswordItem="item"
v-model="item.model"
/>
<button #click="method1()">Click to see changes</button>
</div>
</template>
<script>
export default {
name: "SomeParentComponent",
data() {
return {
listPassword: [
{ model: "passaword" },
{ model: "newPassword" },
{ model: "confirmNewPassword" },
],
};
},
methods: {
method1() {
this.listPassword.forEach((item) => {
console.log(item.model);
});
},
},
}
</script>
And in your ProfilePasswordField you can emit the input event to listen to the respected changes in v-model binding. For example, the ProfilePasswordField component could be like this-
<template>
<v-text-field :value="value" #input="$emit('input', $event)"/>
</template>
<script>
export default {
name: "ProfilePasswordField",
props: {
value: {
required: true
}
}
</script>
In the parent component's console, you should see the changes made by the child component.

Vue, separate component fires off 'not a function' message

Everything here displays and acts how i want except for my call to the setInputName function. I believe the reason for this is because that happens within the tabs component which is built on a separate component and template, which then uses another component/template tab for the individual list item tabs.
The problem here is that when I click my list items, the console prints that _vm.setInputName is not a function
How can I fix this to be able to call this function from within the rendered template?
<tabs>
<tab name="Activity" :selected="true">
<div class="row notesInput" id="notesInput">
<div class="col-lg-12">
<div class="tabs">
<ul style="border-bottom:none !important; text-decoration:none">
<li v-on:click="setInputName('public')">Public</li>
<li v-on:click="setInputName('public')">Internal</li>
</ul>
</div>
<div>
<input type="text" v-bind:name="inputName">
<br>
Input name is: {{ inputName }}
</div>
</div>
</div>
</tab>
</tabs>
<script>
Vue.component('tabs', {
template: `
<div>
<div class="tabs">
<ul>
<li v-for="tab in tabs" :class="{ 'is-active': tab.isActive }">
<a :href="tab.href" #click="selectTab(tab)">{{ tab.name }}</a>
</li>
</ul>
</div>
<div class="tabs-details">
<slot></slot>
</div>
</div>
`,
data() {
return {
tabs: [],
};
},
created() {
this.tabs = this.$children;
},
methods: {
selectTab(selectedTab) {
this.tabs.forEach(tab => {
tab.isActive = tab.name == selectedTab.name;
});
} } });
Vue.component('tab', {
template: `
<div v-show="isActive"><slot></slot></div>
`,
props: {
name: { required: true },
selected: { default: false } },
data() {
return {
isActive: false,
};
},
computed: {
href() {
return '#' + this.name.toLowerCase().replace(/ /g, '-');
} },
mounted() {
this.isActive = this.selected;
} });
export default {
components: {
Multipane,
MultipaneResizer,
},
data () {
return {
inputName: '',
}
},
computed: {
},
methods: {
setInputName(str) {
this.inputName = str;
}
};
</script>
I think the issue is because you are calling the function from another component.
but your function is in another function. If you want this to work then you should extend your component in which your function exists and then you can use it.

Change content on page on clicking a vue js child element

I am creating an app that will display products on a page. Each of these products have a list of features such as "battery power", "charge time", and even just a description that will vary per feature. My question is, how can I make a clickable element, that when clicked will find the data associated with that button/icon, then update the content on the page to reflect this? This content may or may not be in some kind of v-for loop.
See the example of what I have and what I want to achieve below.
Child component:
<template>
<li>
<button #click="$emit('changeProductData', feature)">
<img :src="require('../assets/images/' + feature.item.img)" />
</button>
</li>
</template>
<script>
export default {
props: {
feature: Object
}
}
</script>
Parent component:
<template>
<div>
<div v-for="product in getProduct(productId)" :key="product.productId">
{{ product }}
<Halo
:featuresCount="
`circle-container-` + product.features.length.toString()
"
>
<Feature
v-for="(feature, key, index) in product.features"
:key="index"
:feature="feature"
#changeProductData="something" // this is where we call the custom event
></Feature>
</Halo>
<h1>This is where I want to dynamically inject the title for each feature on clicking corresponding feature</h1>
</div>
</div>
</template>
<script>
import Halo from '#/components/ProductHalo.vue'
import Feature from '#/components/ProductFeature.vue'
import json from '#/json/data.json'
export default {
name: 'ProductSingle',
components: {
Halo,
Feature
},
data() {
return {
products: json
}
},
computed: {
productId() {
return this.$route.params.id
}
},
methods: {
getProduct(id) {
let data = this.products
return data.filter(item => item.productId == id)
},
something(e) {
// ideally we have a method here that grabs the corresponding
//feature then displays it on the page
console.log(e.item.text)
}
}
}
</script>
My console.log call does indeed call the correct title from my data.json as seen below:
[
{
"productId": 1,
"name": "Test 1",
"image": "sample.jpg",
"features": [
{
"item": {
"text": "Something else",
"img": "sample.jpg"
}
},
{
"item": {
"text": "Turbo",
"img": "wine.jpg"
}
},
{
"item": {
"text": "Strong",
"img": "sample.jpg"
}
}
]
}
]
So it seems I can access my title based on the click of each respective item, just not sure how I can display that in an arbitrary location! Any amazing vue js'ers out there who can solve this riddle? TIA
Option1: change the child component
Instead of creating a component only just for one feature button, I would embed the whole list of features in a component with h1 where you want to show the selected feature. So, the child component will look like this:
<template>
<div>
<li v-for="(feature, key, index) in features" :key="index">
<button #click="setFeature(feature)">
<img :src="require('../assets/images/' + feature.item.img)" />
</button>
</li>
<h1>{{ clickedFeuatureText }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
clickedFeuatureText:null
}
},
methods: {
setFeature(e){
this.clickedFeuatureText = e.item.text
}
},
props: {
features: Array
}
}
</script>
And here is the parent component with corresponding changes:
<template>
<div>
<div v-for="product in getProduct(productId)" :key="product.productId">
{{ product }}
<Halo
:featuresCount="
`circle-container-` + product.features.length.toString()
"
>
<Feature :features=" product.features"></Feature>
</Halo>
</div>
</div>
</template>
<script>
import Halo from '#/components/ProductHalo.vue'
import Feature from '#/components/ProductFeature.vue'
import json from '#/json/data.json'
export default {
name: 'ProductSingle',
components: {
Halo,
Feature
},
data() {
return {
products: json
}
},
computed: {
productId() {
return this.$route.params.id
}
},
methods: {
getProduct(id) {
let data = this.products
return data.filter(item => item.productId == id)
}
}
}
</script>
Option2: create the new component for one product
Another way is to leave a child component as is, but create the new component representing one product in the list:
<template>
<div>
{{ product }}
<Halo
:featuresCount="
`circle-container-` + product.features.length.toString()
"
>
<Feature
v-for="(feature, key, index) in product.features"
:key="index"
:feature="feature"
#changeProductData="setFeatureText"
></Feature>
</Halo>
<h1>{{ clickedFeature}}</h1>
</div>
</div>
</template>
<script>
import Feature from '#/components/ProductFeature.vue'
import Halo from '#/components/ProductHalo.vue'
export default {
name: 'product',
components: {
Feature,
Halo
},
data () {
return {
clickedFeature: null
}
},
props: {
product: Object
},
methods: {
setFeatureText(e) {
this.clickedFeature = e.item.text
}
}
}
</script>
Then use the new Product component:
<template>
<div>
<product
v-for="product in getProduct(productId)"
:key="product.productId"
:product="product"
></product>
</div>
</template>
<script>
import Product from '#/components/Product.vue'
import json from '#/json/data.json'
export default {
name: 'ProductSingle',
components: {
Product
},
data() {
return {
products: json
}
},
computed: {
productId() {
return this.$route.params.id
}
},
methods: {
getProduct(id) {
let data = this.products
return data.filter(item => item.productId == id)
}
}
}
</script>

how to uncheck answered question in child vue from parent

My application is similar to a quiz. each question may have radio button answers, integers, text etc.
Consider the case for multiple choice question. I have a vue for the Radio button options. in the parent Vue i have a reset button for each of the question. If I click on the reset, it should removed the selected answer for that particular question.
How can I achieve this given that the reset button is in Parent vue and the answer to be reset is in the child Vue?
Parent:
<template>
<div class="inputContent">
<p class="lead" v-if="title">
{{title}}
<span v-if="valueConstraints.requiredValue" class="text-danger">* .
</span>
</p>
<b-alert variant="danger" show v-else>
This item does not have a title defined
</b-alert>
<!-- If type is radio -->
<div v-if="inputType==='radio'">
<Radio :constraints="valueConstraints" :init="init"
:index="index" v-on:valueChanged="sendData" />
</div>
<!-- If type is text -->
<div v-else-if="inputType==='text'">
<TextInput :constraints="valueConstraints" :init="init" v-
on:valueChanged="sendData"/>
</div>
<div class="row float-right">
<b-button class="" variant="default" type=reset #click="reset">
Reset1
</b-button>
<b-button class="" variant="default" v-
if="!valueConstraints.requiredValue" #click="skip">
Skip
</b-button>
</div>
</div>
</template>
<style></style>
<script>
import { bus } from '../main';
import Radio from './Inputs/Radio';
import TextInput from './Inputs/TextInput';
export default {
name: 'InputSelector',
props: ['inputType', 'title', 'index', 'valueConstraints',
'init'],
components: {
Radio,
TextInput,
},
data() {
return {
};
},
methods: {
skip() {
this.$emit('skip');
},
// this emits an event on the bus with optional 'data' param
reset() {
bus.$emit('resetChild', this.index);
this.$emit('dontKnow');
},
sendData(val) {
this.$emit('valueChanged', val);
this.$emit('next');
},
},
};
</script>
the child vue:
<template>
<div class="radioInput container ml-3 pl-3">
<div v-if="constraints.multipleChoice">
<b-alert show variant="warning">
Multiple Choice radio buttons are not implemented yet!
</b-alert>
</div>
<div v-else>
<b-form-group label="">
<b-form-radio-group v-model="selected"
:options="options"
v-bind:name="'q' + index"
stacked
class="text-left"
#change="sendData"
>
</b-form-radio-group>
</b-form-group>
</div>
</div>
</template>
<style scoped>
</style>
<script>
import _ from 'lodash';
import { bus } from '../../main';
export default {
name: 'radioInput',
props: ['constraints', 'init', 'index'],
data() {
return {
selected: null,
};
},
computed: {
options() {
return _.map(this.constraints['itemListElement'][0]['#list'], (v) => {
const activeValueChoices = _.filter(v['name'], ac => ac['#language'] === "en");
return {
text: activeValueChoices[0]['#value'],
value: v['value'][0]['#value'],
};
});
},
},
watch: {
init: {
handler() {
if (this.init) {
this.selected = this.init.value;
} else {
this.selected = false;
}
},
deep: true,
},
},
mounted() {
if (this.init) {
this.selected = this.init.value;
}
bus.$on('resetChild', this.resetChildMethod);
},
methods: {
sendData(val) {
this.$emit('valueChanged', val);
},
resetChildMethod(selectedIndex) {
this.selected = false;
},
},
};
</script>
One way would be to use an event bus
in your main js add:
//set up bus for communication
export const bus = new Vue();
in your parent vue:
import {bus} from 'pathto/main.js';
// in your 'reset()' method add:
// this emits an event on the bus with optional 'data' param
bus.$emit('resetChild', data);
in your child vue
import {bus} from 'path/to/main';
// listen for the event on the bus and run your method
mounted(){
bus.$on('resetChild', this.resetChildMethod());
},
methods: {
resetChildMethod(){
//put your reset logic here
}
}

uncheck all checkboxes from child component Vue.js

I have the following problem: I have parent component with a list of checkboxes and two inputs. So when the any of those two inputs has been changed I need to uncheck all checkboxes. I would appreciate if you can help me to solve this.
I wanted to change checkedItem to trigger watch in child and then update all children, but it doesn't work.
parent.vue
<template>
<div class="filter-item">
<div class="filter-checkbox" v-for="item in filter.items">
<checkbox :item="item" v-model="checkedItem"> {{ item }} </checkbox>
</div>
<div class="filter-range">
<input v-model.number="valueFrom">
<input v-model.number="valueTo">
</div>
</div>
</template>
<script>
import checkbox from '../checkbox.vue'
export default {
props: ['filter'],
data() {
return {
checkedItem: false,
checkedItems: [],
valueFrom: '',
valueTo: '',
}
},
watch: {
'checkedItem': function () {},
'valueFrom': function () {},
'valueTo': function () {}
},
components: {checkbox}
}
</script>
child.vue
<template>
<label>
<input :value="value" #input="updateValue($event.target.value)" v-model="checked" class="checkbox"
type="checkbox">
<span class="checkbox-faux"></span>
<slot></slot>
</label>
</template>
<script>
export default {
data() {
return {
checked: ''
}
},
methods: {
updateValue: function (value) {
let item = this.item
let checked = this.checked
this.$emit('input', {item, checked})
}
},
watch: {
'checked': function () {
this.updateValue()
},
},
created: function () {
this.checked = this.value
},
props: ['checkedItem', 'item']
}
</script>
When your v-for renders in the parent component, all the rendered filter.items are bound to the same checkedItem v-model.
To correct this, you would need to do something like this:
<div class="filter-checkbox" v-for="(item, index) in filter.items">
<checkbox :item="item" v-model="item[index]> {{ item }} </checkbox>
</div>
To address your other issue, updating the child component list is as easy as updating filter.items.
You don't even need a watcher if you dont want to use one. Here is an alternative:
<input v-model.number="valueFrom" #keypress="updateFilterItems()">
And then:
methods: {
updateFilterItems () {
// Use map() or loop through the items
// and uncheck them all.
}
}
Always ask yourself twice if watch is necessary. It can create complexity unnecessarily.

Categories

Resources