Vuejs - How to communicate changes between parent and child in scoped slots - javascript

I'm trying to create a component which has two slots. The second slot is repeating based on the number of items in the first slot. I have achieved this using scoped slots, however, when the data is updated on the first slot, the second slot does not automatically update it until an event is triggered, eg: click of a button which calls a method.
Is there a way to make the second slot updates its view when the data is changed on the first slot?
Here is the example that I have:
Jsfiddle: https://jsfiddle.net/89vykm75/1/
new Vue({
el: '#app',
components: {
'repeat-for-each-item': {
data: function() {
return {
items: []
}
},
template: `<div>
<slot name="item" v-for="item in items" :item="item"></slot>
<button #click="addItem()">Add item</button>
<slot name="repeat" v-for="item in items" :item="item"></slot>
</div>
`,
methods: {
addItem() {
this.items.push({});
}
}
}
}
});
<div id="app">
<repeat-for-each-item>
<template slot="item" scope="props">
<div>
<input type="text" v-model="props.item.name">
</div>
</template>
<template slot="repeat" scope="props">
<div>
<label>
<span v-if="props.item.name">{{props.item.name}}:</span>
<span v-else>No Name:</span>
</label>
<input type="text">
</div>
</template>
</repeat-for-each-item>
</div>

I found a solution by calling a method on keyup.
Basically, I added #keyup event on the slot
<input type="text" v-model="props.item.name" #keyup="props.onchange()">
And on the component template, pass on the onchange method to the slot
<slot name="item" v-for="item in items" :item="item" :onchange="onchange"></slot>
And then have the onchange function to force the re-render
onchange:() => {
// hack to trigger changes
this.$set(this.items, 0, this.items[0]);
}
Here is the full working JsFiddle: https://jsfiddle.net/89vykm75/2/
I wonder if there is a cleaner solution?

You are falling into a Vue change detection caveat. The issue here is if you add a property to an object that didn't exist before when the object was added to the Vue data, then Vue cannot detect the change. Here is the problem:
this.items.push({})
You're adding an object with no properties, and then you bind v-model to the name property of that object, which does not exist. Vue cannot detect the change, and does not update the other items bound to that property.
If you instead did this:
this.items.push({name: null})
You will find your code works. Here is an updated fiddle.

Related

update data in slot vuejs

hi im using vuejs with laravel project
and this is my vuejs code
Vue.component('search_and_select',{
template:
'<div>'+
'<slot :test_text="test_text"></slot>'+
'</div>',
data:function(){
return {
test_text:"test text",
}
},
methods:{
},
props:{
},
});
new Vue({
el:'.user_search_and_select',
data:{
},
});
and this is my html code
<div is='search_and_select'>
<div slot-scope="{test_text}">
#{{test_text}}
<input type='text' v-model='test_text' />
</div>
</div>
till now everything working so good
but if i keyup <input type='text' v-model='test_text' /> the test_text dont change still the same
so how can i change in slot and change in parent component too
thanks a lot ..
You have to expose a method to the slot for updating the value. This means you won't be able to use v-model because you will need to handle :value and #input separately now.
<slot :test_text="test_text" :update_test_text="update_test_text"></slot>
methods: {
update_test_text(value) {
this.test_text = value
}
}
Now you can use the component like this:
<search_and_select>
<div slot-scope="{ test_text, update_test_text }">
<input
type="text"
:value="test_text"
#input="update_test_text($event.target.value)"
>
</div>
</search_and_select>

In Vue&Vuex, how can I activate onclick method in child component?

I'm trying to edit JS library that already existed but it consisted of Vue. So I studied Vue a little.
The problem is that I made child component called 'Analysis' and want to anchor function. I made tag and bind 'moveAnchor' method to onclick, also declared 'moveAnchor' on methods part. but it didn't work. How can I fix? I'm sorry for being inexperienced.. :(
it is script.js of analysis.
import { mapActions, mapState } from 'vuex';
export default {
name: 'Analysis',
computed: {
checkName : function(){
var flag = this.$store.state.analysis.name;
if(flag.indexOf("/PCs/") != -1){
console.log(flag);
}
}
},
methods: {
moveAnchor: function (id){
var div = document.getElementById(id).scrollIntoView();
}
it is template.html of analysis.
<div :class="$style.scrollarea">
<div :class="$style.dropdown">
<button :class="$style.dropbtn">Analysess</button>
<div :class="$style.dropContent">
<a v-for="item in analyData" v-bind:key="item.id" #onclick="moveAnchor(item.id)">
{{ item.title }}
</a>
</div>
</div>
<span>
{{ checkName }}
</span>
<div v-for="item in analyData">
<h1 v-bind:id="item.id">{{ item.title }}</h1>
<img v-bind:src="item.src" style="width: 100%; height: auto">
</div>
Welcome to StackExchange!
The correct binding for Vue's click event is v-on:click, or #click for shorthand. So when you write #onclick, Vue will never call that.
Just change #onclick to #click and all should work fine.

Dynamic component insertion using VueJs, Cordava and Javascript

Using vuejs and Cordova, I could not dynamicaly insert/create a new v-select component.
My goal is to duplicate and insert a copy an existing v-select when my "Add" button gets clicked.
The code of my page is:
<v-select
:items="exerciceList"
v-model="selectedExercice"
label="pick an exercise"
v-validate="'required'"
data-vv-name="select"
required>
</v-select>
</div>
<div id="selectsContainer"></div>
<v-btn flat icon id="btn" v-on:click="newExercice()">
My (not working) click event listener:
methods: {
newExercice: function () {
var container = document.getElementById('selectsContainer');
var select = document.getElementById('exo');
// select.items = this.exerciceList;
select.removeAttribute('id');
select.style.display = 'block';
container.appendChild(select);
}
},
The item with the id expo's code:
<v-select
id="exo"
style="display: none;"
:items="exerciceList"
v-model="selectedExercice"
label="Choisissez un exercice"
v-validate="'required'"
data-vv-name="select"
required>
Thank you for helping me improve my implementation.
What Michael S has said is correct, you do not want to manipulate the DOM directly, causes many issues. Probably the best solution (quick and easy anyway) would be to import that additional v-select component and then add it dynamically with a simple list or in this case an integer increase.
If you're wanting to get a little more customized with it, you can create an object and add and remove styles, attr, etc. from it dynamically the same way this option would work but turning item into an array of objects. then proceed to add an object to it whenever the button is clicked.
<v-select
:items="exerciceList"
v-model="selectedExercice"
label="pick an exercise"
v-validate="'required'"
data-vv-name="select"
required>
</v-select>
</div>
<div id="selectsContainer"
v-for="(item, idx) in items">
<v-select
id="item.id" <!-- or use idx -->
style="item.style" <!-- or use any string statement -->
...etc
>
</div>
<v-btn flat icon id="btn" v-on:click="newExercice()">
...{
components: [componentName],
data: return {
items: 0,
...
}
methods: {
newExercice: ()=>{
this.items++
}
}
...}

Vue.js change model attached to a form upon clicking a list

I have an array of objects. These objects are loaded into a list in vue.js.
Aside from this list, I have a form that displays data from one of these objects. I want to, when clicking one of the list's elements, it will bind this specific object to the form and show its data.
How can do this in Vue.js?
My list code is:
<div id="app-7">
<ul id="food-list" v-cloak>
<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>
<li class="food">
<div class="food-header">
<img :src="'img/' + food.slug +'.png'">
<div class="food-title">
<p>{{food.name}} |
<b>{{food.slug}}</b>
</p>
<p>quantity: {{food.quantity}}</p>
</div>
<div class="food-load"> // load into form upon clicking this
</div>
</div>
</li>
</food-item>
</ul>
</div>
Since I do not have the code for the form, this is my best guess without clarification.
You can add a click handler to the item you want to be clicked. It will pass the value of the food item into the method.
<div class="food-load" #click="setFoodItem(item)">
</div>
And when that method is called, it can assign the clicked item to a data property. I'm not sure where your form is, and if it is in a different component. If it is in a child component, you would have to pass it in as a prop, or emit an event to pass it to a parent component.
data() {
return {
//create a reactive field to store the current object for the form.
foodItemForm: null
};
},
methods: {
//method for setting the current item for the form.
setFoodItem(item) {
this.foodItemForm = item;
}
}
Missing quite a bit of info in your sample code, your script is very important to see to make sense of what you would like to accomplish and where things might be going wrong.
Here's a quick list of the issue I came across with your code:
v-for refers to an individual food item as 'item', inside the loop you're trying to access properties as 'food'
You don't wrap your code in a component unless you're importing the component
When binding a value to 'v-bind:src' (or shorthand ':src') only pass the url, you should be specifying this in your script not inline.
You're better off using a button and the 'v-on:click' (or shorthand '#click') to load your selected food item into your form
You should also include your Javascript
Regardless, here's how I would handle this (took the liberty in filling in some blanks):
<template>
<div id="app">
<ul id="food-list">
<!--<food-item v-for="item in foodList" v-bind:food="item" v-bind:key="item.id" inline-template>-->
<li v-for="item in foodList" class="food">
<div class="food-header">
<img :src="item.slug" v-bind:alt="item.slug" width="250px" height="auto">
<div class="food-title">
<p>{{item.name}} | <b>{{item.slug}}</b></p>
<p>quantity: {{item.quantity}}</p>
</div>
<button class="food-load" #click="loadFoodItem(item.id)">Load Food Item</button>
</div>
</li>
<!--</food-item>-->
</ul>
<form v-if="activeFoodId != null" id="foodItemForm" action="#">
<h3>Food Form</h3>
<label for="food-id">Id:</label>
<input id="food-id" type="number" v-bind:value="foodList[activeFoodId].id"><br/>
<label for="food-slug">Slug:</label>
<input id="food-slug" type="text" v-bind:value="foodList[activeFoodId].slug"><br/>
<label for="food-name">Name:</label>
<input id="food-name" type="text" v-bind:value="foodList[activeFoodId].name"><br/>
<label for="food-quantity">Quantity:</label>
<input id="food-quantity" type="number" v-bind:value="foodList[activeFoodId].quantity">
</form>
</div>
</template>
<script>
export default {
name: 'app',
data: function () {
return {
activeFoodId: null,
foodList: [
{
id: 1,
slug: 'http://3.bp.blogspot.com/-QiJCtE3yeOA/TWHfElpIbkI/AAAAAAAAADE/Xv6osICLe6E/s320/tomato.jpeg',
name: 'tomatoes',
quantity: 4
}, {
id: 2,
slug: 'https://img.purch.com/rc/300x200/aHR0cDovL3d3dy5saXZlc2NpZW5jZS5jb20vaW1hZ2VzL2kvMDAwLzA2NS8xNDkvb3JpZ2luYWwvYmFuYW5hcy5qcGc=',
name: 'bananas',
quantity: 12
}, {
id: 3,
slug: 'https://media.gettyimages.com/photos/red-apples-picture-id186823339?b=1&k=6&m=186823339&s=612x612&w=0&h=HwKqE1MrsWrofYe7FvaevMnSB89FKbMjT-G1E_1HpEw=',
name: 'apples',
quantity: 7
}
]
}
},
methods: {
loadFoodItem: function (foodItemId) {
console.log(foodItemId)
this.activeFoodId = foodItemId
}
}
}
</script>
<style>
/# Irrelevant #/
</style>
Hope it helps!

Vue 2: v-for dynamic props in component

I have a Vue component in which I'm trying to do a v-for of an multi array json object passed by props, this object is dynamic and is populated by a parent method.
Here:
My problem is that in the component i'm seeing only the first object of the data:
but I have no error in console, so I don't understand what the problem is... must I do a watch in data?
Here is my code:
<lista-percorso :selezionati="il_tuo_percorso"></lista-percorso>
COMPONENT
Vue.component('lista-percorso', {
template:`
<div class="container" style="margin-top:30px;">
<template v-if="listaSelezionati.length>0">
<div class="row" v-for="(selezionato,index) in listaSelezionati">
<div>{{selezionato[index]}}</div>
</div>
</template>
<template v-else>
<h5>NON HAI SELEZIONATO NESSUN SERVIZIO</h5>
</template>
</div>
`,
props: ['selezionati'],
data: function(){
return{
listaSelezionati:this.selezionati
}
},
methods:{
}
});
Your data listaSelezionati is an Array of Arrays of Objects: [[{one:one}],[{two,two}]]
when you go this:
<div class="row" v-for="(selezionato,index) in listaSelezionati">
<div>{{selezionato[index]}}</div>
</div>
You are telling Vue to render the first item [{one:one}] and then the index in that item {one:one}. However, since they all appears to be arrays with length 1, you could do this:
<div class="row" v-for="(selezionato,index) in listaSelezionati">
<div>{{selezionato[0]}}</div>
</div>

Categories

Resources