Error with prop definition in vuejs - vuejs2

I use datatable (https://github.com/pstephan1187/vue-datatable) component in VueJs 2.
My component is the following:
<template>
<div>
<div id="desktop">
<div v-if="visibility.personsTable">
<datatable-persons
:columns="persons_table_columns"
:data="rows"
filterable paginate
></datatable-persons>
</div>
</div>
</div>
</template>
<script>
import VueJsDatatable from 'vuejs-datatable';
import Profile from './user/Profile';
import ConnectionService from '../components/services/ConnectionService';
const connectionService = new ConnectionService();
Vue.component('showuser', {
template: `
<button class="btn btn-xs btn-primary" #click="goToUpdatePage">Профиль</button>
`,
props: [row],
methods: {
goToUpdatePage: function(){
}
}
});
export default {
components: {
datatablePersons: VueJsDatatable,
usersTable: VueJsDatatable,
},
data() {
return {
rows: [],
persons_table_columns: [
{label: 'id', field: 'id'},
{label: 'Имя', field: 'firstname'},
{label: 'Фамилия', field: 'lastname'},
{label: 'Отчетство', field: 'middlename'},
{label: 'Профиль', component: 'showuser'}
],
visibility: {
personsTable: false,
}
}
},
methods: {
showPersons() {
this.$http.get(hostname + 'name=person_list&session_id=' +
sessionApiId).then(response => {
this.rows = connectionService.renderMultipleInstances(response.body);
this.visibility.usersTable = false;
this.visibility.personsTable = true;
}, response => {
// error callback
});
},
}
}
</script>
I have the following error:
Uncaught ReferenceError: row is not defined
at Object.defineProperty.value (app.js:5309)
at webpack_require (app.js:20)
Due to documentation of "table" component, it should watch for "row prop" And using official example it works properly.

In the definition of your showuser component props should be an array of strings:
props: ['row']
These strings should match the names of the attributes you use to pass data to the component.
Also from the sinppet I would guess you want it to be 'rows' not 'row'.

Related

Nuxt Dynamic V-model

I'm having difficulty binding my custom input to data(). I've tried several combinations to try to get it working and so far only the placeholder seems to work. I created an array called questions and its content is dynamically rendered to the page. On page load, my code determines if this is either a user or business account and then sets the value of the questions array based on the result which works fine. I created a test function to test if the v-model binding is working but I get an empty alert. I find it strange that the placeholder works just fine but not the v-modal bind.
<template>
<section>
<form>
<BaseInput v-for="question in questions"
v-model="question.bind" :placeholder="question.placeholder"/>
</form>
<button #click="test"></button>
</section>
</template>
<script>
import BaseInput from '../BaseInput.vue'
export default {
components: {
BaseInput,
},
data(){
return{
firstName: '',
lastName: '',
commercialName: '',
businessName: '',
questions: [],
userQuestionsArray: [
{ bind: 'firstName', placeholder: 'First Name' },
{ bind: 'lastName', placeholder: 'Last Name' },
],
businessQuestionsArray: [
{ bind: 'commercialName', placeholder: 'Commercial Name' },
{ bind: 'businessName', placeholder: 'Business Name' },
]
}
}
},
methods: {
test(){
alert(this.password)
}
},
mounted() {
if(this.$store.state.userType === 'Personal'){
this.questions = this.userQuestionsArray;
}else {
this.questions = this.businessQuestionsArray;
}
},
computed: {
userType: {
get () {
return this.$store.state.userType
}
}
}
}
</script>
you cant use v-mode in v-for. you must use wrapper like template or tag over each input.
<template>
<section>
<form v-for="(question, index) in questions" :key="index">
<BaseInput v-model="question.bind" :placeholder="question.placeholder"/>
</form>
<button #click="test"></button>
</section>
</template>

Passing Functions as props in vue js

I'm trying to pass editTodo as props function from parent app.vue to child components ...
TodoItem.vue component there is a list Of Items and Time returns to main user input of newTodo and dateTime field. Actually, I'm a new learner of Vue js there is a little knowledge of pass props b/w the components communication.
<template>
<div id="app" class="container">
<TodoInput :addTodo="addTodo"
:updateTodo="updateTodo"
/>
<todo-item v-for="(todo, index) in todos"
:key=todo.id
:todo=todo
:index =index
:removeTodo="removeTodo"
:editTodo="editTodo" />
</div>
</template>
<script>
import TodoInput from "./components/TodoInput.vue";
import TodoItem from "./components/TodoItem.vue";
export default {
name: "App",
components: {
TodoInput,
TodoItem,
},
data() {
return {
editing:false,
editItems:{},
todos: [
// {
// id: 1,
// title: "",
// date: new Date(),
// editing: false,
// completed: false,
// },
// {
// id: 1,
// title: "",
// date: new Date(),
// editing: false,
// completed: false,
// },
],
};
},
methods: {
editTodo(index, newTodo, dateTime){
, ' dateTime ', dateTime)
// this.editItems = {
// id,
// title,
// time,
// }
this.todo = newTodo
this.todo = dateTime
this.selectedIndex = index
this.editing = true
},
TodoItem.vue component there is a list Of Items and Time returns to main user input of newTodo and dateTime field.***enter code here
`**
-->
{{todo.title}}
{{todo.time}}
</div>
<div class="remove-item" #click="removeTodo(index)">
×
</div>
<div class="edit-item" #click="eiditTodo(index)"
>
<i class="fas fa-edit" id="edit"></i>
</div>
export default {
name: 'todo-item',
props:{
todo:{
type: Object,
required: true,
},
removeTodo:{
type:Function,
required:true,
},
index:{
type:Number,
required: true,
},
},
data(){
return{
'id': this.todo.id,
'title': this.todo.newTodo,
'time': this.todo.dateTime,
}
methods:
getEdit(){
this.$emit('editTodo', this.selectedIndex)
}
**`
Instead of passing a function as a prop to the child component in order to run it, you should instead emit an event from the child component and execute the function in the parent component.
To emit an event from the child component
#click="$emit('edit-todo')"
And then in the parent component
<div #edit-todo="editTodo">
</div>
Alternatively, you could just define the editTodo function in theTodoItem component and call it directly.

Expected array got function. Passing function into component vuejs

I am trying to pass a function into my component and I keep getting this error back. "Invalid prop: type check failed for prop "form_type". Expected Array, got Function." My function returns an array so I am a little lost on how to fix this.
The function I am referencing is selectedType & the component in question is ChildTab
<template>
<div class="row">
<q-field
label="Contact Type"
:labelWidth="3"
error-label="Please select a contact type"
:error="!!failed_validations.contact_type"
>
<q-select v-model="contact_type" :options="contact_types"/>
</q-field>
</div>
<ChildTabs
:form_type="selectedType"
/>
<q-field class="float-right">
<q-btn color="faded" v-on:click="goBack()">Cancel</q-btn>
<q-btn color="green-6" v-on:click="selectedType()">Submit</q-btn>
</q-field>
</div>
</div>
</template>
<script>
'use strict';
import ChildTabs from '../tabs';
export default {
name: 'contacts-view',
data: function () {
return {
contact_type: '',
contact_types: [
{
label: 'Pregnancy',
value: 'pregnancy',
form_type: [
'BreastFeeding',
'Pregnancy'
]
},
{
label: 'Post Partum (Includes Birth)',
value: 'postpartum',
form_type: [
'Birth',
'BreastFeeding',
'PostPartum'
]
},
{
label: '1 - 2 Month',
value: '1_2_months',
form_type: [
'BreastFeeding',
'DuoMonths'
]
},
{
label: '6 Month',
value: '6_months',
form_type: [
'SixMonth'
]
}
],
}
},
props: {
},
computed: {
selectedType: function ()
{
var values = this.contact_types.map(function(o) { return o.value });
var index = values.indexOf(this.contact_type);
this.selectedForms = this.contact_types[index].form_type
// console.log(this.selectedForms);
return this.selectedForms;
}
},
methods: {
},
created: function () {
this.selectedType();
},
components: {
ChildTabs
}
}
</script>
As you try to call selectedType on click "Submit", maybe you should call it as a method.
Inside selectedType you bind a selectedForms property. Why don't you just initialize this property inside data as an empty array and pass it as a props of your ChildTabs component ?
<template>
<div class="row">
<ChildTabs :form_type="selectedForms" />
</div>
</template>
export default {
name: 'contacts-view',
data: function () {
return {
selectedForms: [],
// ...
}
},
methods: {
selectedType() {
var values = this.contact_types.map(function(o) { return o.value });
var index = values.indexOf(this.contact_type);
this.selectedForms = this.contact_types[index].form_type
}
},
//...
}
Fiddle example
What you bind as a prop in a component goes as same in the component. So as you're referencing selectedType in your ChildTabs component - the method selectedType will be received by ChildTabs as a prop. So either you edit your propType in ChildTabs component and invoke that passed method as needed or you call the selectedType method on the fly when passed in as a prop like
<ChildTabs :form_type="selectedType()" />
This will call that method then and will bind the resulting array as prop

Vue.js Component with v-model

I have been able to accomplish a single level deep of v-model two-way binding on a custom component, but need to take it one level deeper.
Current working code:
<template lang="html">
<div class="email-edit">
<input ref="email" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Used like this: <email-edit-input v-model="emailModel" />
However, if I add this piece, the value no longer propagates upwards:
<div class="email-edit">
<line-editor ref="email" :title="'Email'" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Using this second custom component:
<template lang="html">
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" ref="textInput" type="text" :value="value" #input="updateInput()" />
</div>
</template>
<script type="text/javascript">
export default {
components: {
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input', this.$refs.textInput.value)
}
},
data: function(){
return {}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
</script>
The first code-block works fine with just an input. However, using two custom components does not seem to bubble up through both components, only the LineEditor. How do I get these values to bubble up through all custom components, regardless of nesting?
I've updated your code a bit to handle using v-model on your components so that you can pass values down the tree and also back up the tree. I also added watchers to your components so that if you should update the email object value from outside the email editor component, the updates will be reflected in the component.
console.clear()
const LineEditor = {
template:`
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" type="text" v-model="email" #input="$emit('input',email)" />
</div>
`,
watch:{
value(newValue){
this.email = newValue
}
},
data: function(){
return {
email: this.value
}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
const EmailEditor = {
components: {
LineEditor
},
template:`
<div class="email-edit">
<line-editor :title="'Email'" v-model="email" #input="updateInput"/>
<input :value="value.body" v-model="body" #input="updateInput"/>
</div>
`,
watch:{
value(newValue){console.log(newValue)
this.email = newValue.email
this.body = newValue.body
}
},
methods: {
updateInput: function(value){
this.$emit('input', {
email: this.email,
body: this.body
})
},
},
data: function(){
return {
email: this.value.email,
body: this.value.body
}
},
props: {
value: {
default: {
email: "",
body: ""
},
type: Object
}
}
}
new Vue({
el:"#app",
data:{
email: {}
},
components:{
EmailEditor
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<email-editor v-model="email"></email-editor>
<div>
{{email}}
</div>
<button #click="email={email:'testing#email', body: 'testing body' }">change</button>
</div>
In the example above, entering values in the inputs updates the parent. Additionally I added a button that changes the parent's value to simulate the value changing outside the component and the changes being reflected in the components.
There is no real reason to use refs at all for this code.
In my case, having the passthrough manually done on both components did not work. However, replacing my first custom component with this did:
<line-editor ref="email" :title="'Email'" v-model="value.email"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
Using only v-model in the first component and then allowing the second custom component to emit upwards did the trick.

In vue2 v-for nested component props aren't updated after element is removed in parent

For my app I'm using two Vue components. One that renders a list of "days" and one that renders for each "day" the list of "locations". So for example "day 1" can have the locations "Berlin", "London", "New York".
Everything gets rendered ok but after removing the "Day 1" from the list of days the view isn't rendered corrected. This is what happens:
The title of the day that was removed is replaced -> Correct
The content of the day that was removed isn't replaced -> Not correct
Vue.component('day-list', {
props: ['days'],
template: '<div><div v-for="(day, index) in dayItems">{{ day.name }} Remove day<location-list :locations="day.locations"></location-list><br/></div></div>',
data: function() {
return {
dayItems: this.days
}
},
methods: {
remove(index) {
this.dayItems.splice(index, 1);
}
}
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: '<div><div v-for="(location, index) in locationItems">{{ location.name }} <a href="#" #click.prevent="remove(index)"</div></div>',
data: function() {
return {
locationItems: this.locations
}
},
methods: {
remove(index) {
this.locationItems.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [
{
name: 'Day 1',
locations: [
{name: 'Berlin'},
{name: 'London'},
{name: 'New York'}
]
},
{
name: 'Day 2',
locations: [
{name: 'Moscow'},
{name: 'Seul'},
{name: 'Paris'}
]
}
]
}
},
methods: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Please use Vue-devtools if you are not already using it. It shows the problem clearly, as seen in the image below:
As you can see above, your day-list component comprises of all the days you have in the original list, with locations listed out directly. You need one more component in between, call it day-details, which will render the info for a particular day. You may have the location-list inside the day-details.
Here is the updated code which works:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Vue.component('day-list', {
props: ['days'],
template: `
<div>
<day-details :day="day" v-for="(day, index) in days">
Remove day
</day-details>
</div>`,
methods: {
remove(index) {
this.days.splice(index, 1);
}
}
});
Vue.component('day-details', {
props: ['day'],
template: `
<div>
{{ day.name }}
<slot></slot>
<location-list :locations="day.locations"></location-list>
<br/>
</div>`
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: `
<div>
<div v-for="(location, index) in locations">
{{ location.name }}
[x]
</div>
</div>
`,
methods: {
remove(index) {
this.locations.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [{
name: 'Day 1',
locations: [{
name: 'Berlin'
}, {
name: 'London'
}, {
name: 'New York'
}]
}, {
name: 'Day 2',
locations: [{
name: 'Moscow'
}, {
name: 'Seul'
}, {
name: 'Paris'
}]
}]
}
},
methods: {}
});
One other thing - your template for location-list has an error - you are not closing the <a> element. You may use backtick operator to have multi-line templates as seen in the example above, to avoid template errors.
Also you are not supposed to change objects that are passed via props. It works here because you are passing objects which are passed by reference. But a string object getting modified in child component will result in this error:
[Vue warn]: Avoid mutating a prop directly...
If you ever get this error, you may use event mechanism as explained in the answer for this question: Delete a Vue child component