Can't re-render html in vee-validate component - javascript

Vue.js v2.6.11 / vee-validate v3.2.2
I have a button that will push new element to form.demand (data in vue app) on click event.
And if form.demand update, html in v-for should be updated.
After I wrap it in vee-validate component , it not works.
form.demand will update, but v-for won't.
I try to put same html in test-component, when form.demand update, v-for update too.
I can't figure out why...
following is my code:
HTML
<div id="content">
<test-component>
<div v-for="demand in form.demand">{{demand}}</div>
</test-component>
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in form.demand">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
</validation-provider>
</div>
javascript
Vue.component('validation-provider', VeeValidate.ValidationProvider);
Vue.component('validation-observer', VeeValidate.ValidationObserver);
Vue.component('test-component',{
template: `
<div>
<slot></slot>
</div>
`
})
var app = new Vue({
el: "#content",
data: {
form: {
demand: [],
},
},
methods: {
addDemand(){
this.form.demand.push({
name : "demand name",
count: 1
})
}
})
------------Try to use computed & Add :key----------------
It's still not work. I get same result after this change.
HTML
<validation-provider rule="" v-slot="v">
<div #click="addDemand">new</div>
<div v-for="(demand,index) in computed_demand" :key="index">
<!--.........omitted.........-->
</validation-provider>
Javascript
var app = new Vue({
el: "#content",
// .......omitted
computed:{
computed_demand() {
return this.form.demand;
}
},
})
I think I found the problem : import Vue from two different source. In HTML, I import Vue from cdn. And import vee-validate like following:
vee-validate.esm.js
import Vue from './vue.esm.browser.min.js';
/*omitted*/
validator.js
import * as VeeValidate from './vee-validate.esm.js';
export { veeValidate };
main.js
// I didn't import Vue from vue in this file
import { veeValidate as VeeValidate } from './validator.js';
Vue.component('validation-provider', VeeValidate.ValidationProvider);
HTML
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- at end of body -->
<script src="/static/javascripts/main.js" type="module"></script>
</body>
After I fix this( import vee-validate from cdn, or import Vue by ES6 module).
It works, although it still have infinite loop issue with vee-validate.
Sorry for I didn't notice that import vue from two different source.

Please provide a key in you v-for. see code below
<div v-for="(demand,index) in form.demand" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, make a computed property that will hold your form.demands array, like this one
computed: {
form_demands: function() {
return this.form.demand
}
}
then call this computed property in your v-for
<div v-for="(demand,index) in form_demands" :key="index">
<div>{{demand.name}}</div>
<div>{{demand.count}}</div>
<input type="text" :name="'demand['+index+'][name]'" v-model="form.demand[index].name" hidden="hidden" />
<input type="text" :name="'demand['+index+'][count]'" v-model="form.demand[index].count" hidden="hidden" />
</div>
Or, use the vue forceUpdate method
import Vue from 'vue';
Vue.forceUpdate();
Then in your component, just call the method after you add demand
this.$forceUpdate();
It is recommended to provide a key with v-for whenever possible,
unless the iterated DOM content is simple, or you are intentionally
relying on the default behavior for performance gains.

Related

Vue component stops working when wrapped into another component

I got stucked with Vue.js. I am trying to basically wrap a component(that is already inside one component) into one more. I have a dropdown with a select and I call a function on change. Everything works fine until I wrap the component in one more on top. The top level one is in blade as it's used with Laravel. Snippets:
Component with dropdown:
<template>
<div id="watchlist-item">
<select #change="changed()" class="form-control"
id="currencies" name="currencyList">
<option value="USD" selected="selected">USD</option>
<option value="EUR">EUR</option>
</select>
</div>
</template>
<script>
export default {
name: "watchlist-item.vue",
methods: {
changed() {
alert("CHANGED");
},
},
}
</script>
Wrapper:
<template>
<div id="watchlistItem">
<watchlist-item></watchlist-item>
</div>
</template>
<script>
export default {
name: "watchlist"
}
</script>
Top component:
<template>
<div id="watchlist">
<watchlist></watchlist>
</div>
</template>
<script>
export default {
name: "main-component"
}
</script>
Blade template:
#extends('layouts.layout')
<div>
{{-- <div id="maincomponent">--}}
{{-- <main-component></main-component>--}}
{{-- </div>--}}
<div id="watchlistItem">
<watchlist-item></watchlist-item>
</div>
</div>
This works fine and i get alert on change. However, when i uncomment the commented part and vice-versa (so basically wrap it one more time) vue stops aletring me. I find this behaviour pretty weird but I am just starting with Vue so maybe its just a small detail I'm missing. I don't really even know what to search for though, so any help would be greatly appreciated. Thank you.
Just make sure that you are importing child components inside it's parent correctly:
main-component > watchlist > watchlist-item
| |
has has
Well it doesnt work because you need to register it via components, but first you need to import it.
<template>
<div id="watchlistItem">
<watchlist></watchlist>
</div>
</template>
<script>
import watchlist from "path/to/watchlist";
export default {
name: "watchlist",
components: {
watchlist: watchlist
}
}

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>

vuejs component hides everything after if not put inside a div

I started using vuejs with parcel. I have a main component App.vue from which I call a subcomponent Hello.vue using <Hello/> in App's template. I have a weird bug if I don't put the <Hello/> inside a div tag, everything that comes after in html doesn't show. The code is below:
index.js
import Vue from "vue";
import App from "./App";
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
App.vue
<template>
<div id="app">
<h3>bla bla</h3>
<div><Hello/></div>
<!-- if not put inside a div, hides everything after-->
<h2>test</h2>
<p>kldsfnlkdsjfldsfds</p>
<h5>skjdnsqkfdnlkdsqf</h5>
</div>
</template>
<script>
import Hello from "./components/Hello";
export default {
name: "App",
components: {
Hello
}
};
</script>
<style>
</style>
Hello.vue
<template>
<div>
<h1>{{ message }}</h1>
<h2>Hello {{ person.firstname}} {{person.lastname}}</h2>
<label>
Firstname:
<input type="text" v-model="person.firstname">
</label>
<label>
Lastname:
<input type="text" v-model="person.lastname">
</label>
<label>
Message:
<input type="text" v-model="message">
</label>
</div>
</template>
<script>
export default {
data() {
return {
person: {
firstname: "John",
lastname: "Doe"
},
message: "Welcome !"
};
}
};
</script>
<style>
</style>
Here is a screenshot of what I get without wrapping <Hello/> with a <div></div>
And then with a div:
Thanks !
EDIT: I don't get an error in the console. I forgot to add that I tried with webpack and I don't get this bug, so It's most likely related to parcel.
Some browsers do not display elements correctly if they use <foo /> without a closing tag, instead of <foo></foo>.
If items are not rendered with the closing tag, this may be your issue.
Some vue components will generate the closing tag from your template, even though you do not have it in your source, and others will not.
When you use a SFC (Single File Component) you must have only one element inside the <template>. Then, inside that one element you can have as many other elements as you like.
Have a look at the "Example sandbox" Simple to do app in the official documentation: https://v2.vuejs.org/v2/guide/single-file-components.html#Example-Sandbox
The file ToDoList.vue is a good example in here: https://codesandbox.io/s/o29j95wx9

Meteor and Vue Cannot find element: #app

I am developing a simple web app with Meteor and Vue and I came across this problem. Cannot find element: #app. I have searched and searched, added some 'solutions' to my problem but still no result.
missionPage.vue
<template>
<div class="ui center aligned container ">
<div class="missionPage">
<h3 class="ui header">Our story</h3>
<p>{{text1}}</p>
<p>{{text2}}</p>
<h1>Trigger question?</h1>
<a href="#" type="button" id="scrollDownArrow"><i class="huge angle double
down icon" ></i></a>
</div>
</div>
</template>
<script>
import { Session } from 'meteor/session';
export default {
data() {
return {
text1:'abc',
text2:'def'
}
},
}
</script>
missionPage.html
<template name='missionPage'>
<div id="app">
</div>
</template>
missionPage.js
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { Vue } from 'meteor/akryum:vue';
import missionPage from '/client/template/missionPage.vue';
window.onload=function(){
var app = new Vue({
el: '#app',
render:h=>h(missionPage)
})
}
It seems that it doesn't recognize the #app id which is mind-blowing in my opinion. I have exactly the same code ran in another app and it works perfectly. I have red other posts and added the 'window.onload' function as it turns out that the reason why the element is not recognized is that it was not loaded but still the error persists.

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

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.

Categories

Resources