this.$emit not passing data to parent element in Vue.js - vue.js

I'm creating a to-do list in vue.js and the following piece of code in the child element returns undefined
<input v-model="titleText" type='text'>
<input v-model="projectText" type='text'>
<button class='ui basic blue button' v-on:click="sendForm()">
Create
</button>
<button class='ui basic red button' v-on:click="closeForm()">
Cancel
</button>
sendForm () {
if (this.titleText.length > 0 && this.projectText.length > 0) {
this.$emit('create-todo', {
title: this.titleText,
project: this.projectText,
done: false
})
}
this.titleText = ''
this.projectText = ''
this.isCreating = false
}
}
Parent element:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo()"></create-todo>
</div>
</template>
methods: {
createTodo (newTodo) {
console.log(newTodo)
this.todos.push(newTodo)
}
}

The problem is a simple one: when you handle the create-todo event, you're explicitly invoking the createTodo() method without allowing any arguments to be passed to it. You must either allow Vue to interpret which arguments to supply implicitly by omitting the parentheses, or use a combination of spread syntax and the arguments object to explicitly pass all arguments provided by the emitted event into the method call.
Using omission:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo"></create-todo>
</div>
</template>
Using spread syntax + arguments object:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo(...arguments)"></create-todo>
</div>
</template>
Either solution is acceptable. If, however, you need to be able to include additional arguments into your method call in the future, then using the spread syntax + arguments object would be necessary with the additional arguments supplied before the event arguments. For example, if you wanted to supply the values "foo" and "bar" to createTodo() as well, then you would do the following:
<template>
<div id="app">
<todo-list v-bind:todos="todos"></todo-list>
<create-todo v-on:create-todo="createTodo('foo', 'bar', ...arguments)"></create-todo>
</div>
</template>

You haven't shown the v-dom of your parent component, but perhaps you didn't insert your #create-todo listener into the custom component tag. I don't have 50 rep, so I can't comment and ask.

Related

Vue - display/create component from a function

One thing that I have been struggling to figure out how to do better is modals. Currently, I am registering the modal component on each Vue that needs it. However, this feels rather sloppy, as I am having to register the component several times. Even using mix-ins just does not feel like an elegant solution. What would be optimal to be able to do is to mimic JavaScript's alert() method on the Vue instance. For example, be able to call this.ShowModal(Header, Body)
However, from my understanding, there is no way to accomplish this
Take for example my Modal example. You could have a modal template like this:
<script type="text/x-template" id="modal-template">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot>
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
default footer
<button class="modal-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
Then you would have to reference the component over and over again like this
<template>
<button #click="displayModal">Display the Modal Alert</button>
<modal v-if="showModal" #close="showModal = false">
<h3 slot="header"> This is a good header </h3>
<p>
Look at me I am the body! You have seen me {{displayCount}} times!
</p>
</modal>
</template>
<script>
components: {modal},
data: {
showModal: false,
displayCount: 0
},
methods: {
displayModal(){
this.displayCount++
this.showModal = true;
}
}
</script>
If you wanted to reuse the component for several messages from within the parent you would then have to add several more variables to store things such as the header and body. You could put some of the logic into a mixin but you would still have to have the clutter of adding the modal component and possibly the mixin.
This brings us to the question. Is there a way to create a function in the Vue instance that would allow for us to dynamically create a Modal component and fill in the slots with arguments passed to the function? e.g. this.ShowModal("This is a good header", "Look at me I am the body!")
Use Vue.extend() create a "modal" constructor and create a instance,you can mount it to DOM dynamically by $mount or other ways
In Modal example:
modal.vue:
<template>
<div>
{{message}} //your modal content
</div>
</template>
<script>
export default {
name: 'modal',
data(){
return {
message: '',
}
},
methods:{
/************/
close () {
/****this.$destroy()****/
}
}
}
</script>
modal.js:
import myModal from 'modal.vue'
import Vue from 'vue'
const modalConstructor = Vue.extend(myModal)
const modal = (options,DOM)=>{
const modalInstance = new modalConstructor({data:options})
modalInstance.vm = modalInstance.$mount() //get vm
const dom = DOM || document.body // default DOM is body
dom.appendChild(modalInstance.vm.$el) // mount to DOM
return modalInstance.vm
}
export default modal
now you can create a Modal component by a function like this:
import showModal from 'modal.js'
showModal({message:"..."})

unexpected vue warn on a declared prop

I'm learning vuex. I'm facing a strange issue after I've migrated some methods to vuex actions.
I get this error in a component that has worked fine until I've migrated some things to vuex and I've implemented inside the component ...mapGetters and ...mapActions
the error is [Vue warn]: Property or method "isVisible" is not defined on the instance but referenced during render
but in my data I've declared the prop
data() {
return {
id: state.userInfo.id,
endCursor: state.userInfo.end_cursor,
nextPageLoaded: false,
isVisible: false,
isVideo: null,
url: null
}
}
<div class="modal fade show" tabindex="-1" role="dialog" v-if="isVisible">
<div class="modal-dialog">
<div class="modal-content h-100 rounded-0">
<div class="modal-header">
<button type="button" class="close mb-3 float-right" #click.prevent="closeZoomModal()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<!-- display image -->
<img class="img-fluid w-100 h-100 img-zoom" :src="url" v-if="!isVideo">
<!-- display video -->
<div class="embed-responsive embed-responsive-4by3 h-100" v-else>
<iframe class="embed-responsive-item h-100" :src="url" title=""></iframe>
</div>
</div>
</div>
</div>
</div>
This happen after the user click on the home component to search for ome data and the result component where the error is fires, is loaded.
How I can fix it? can the error be caused from ...mapGetters or ...mapActions ?
state is not available in data. According to docs, you can pass the instance as first param of the data function
data: vm => ({
isVisible: vm.$store.state.isVisible
})
... but I personally haven't used this (it doesn't work with Typescript and the component is still in an early lifecycle stage and a lot of things are missing from it). Besides, this is merely an assignment (it only runs once - it's not a getter - so if the state changes after data has been set, data won't react to it. You'd have to modify the data prop itself).
So what you need to do is move all store related component properties from data into computed by using either ...mapState() (if they're vuex state props), ...mapGetters() (if they're vuex getters) or use explicit computed syntax:
computed: {
isVisible() {
return this.$store.state.isVisible; // if store state prop
// return this.$store.getters['isVisible'] // if store getter
}
}
If you also want to be able to assign to it (as you would to a data property), you have to replace the above computed syntax (only getter) with a getter + setter syntax:
computed: {
isVisible: {
get() {
return this.$store.state.isVisible;
},
set(value) {
this.$store.dispatch('setVisibility', value);
// you can also commit mutations `this.$store.commit()` from here
}
}
}
If you're still having trouble, please create a minimal reproducible example on codesandbox.io and I'll help sort it out.

How do you pass a prop inside a function inside a template in vue.js?

I am trying to pass a prop inside a function inside a template for a to-do test site I'm making. Basically I want to have a list item which includes the todo item with a button next to it that deletes the same item.
Vue.component("todo-item", {
props: ["todotext"],
template: "<li>{{todotext.text}} <button v-on:click='removeThisItem({{todotext}})'>X</button></li>",
})
var next_id = 3
var app = new Vue ({
el: "#app",
data: {
message: "",
todos: [
{id: 0, text: "Do assignment"},
]
},
methods: {
addTodoItem: function () {
this.todos.push({id: next_id, text: this.message})
next_id += 1
},
removeThisItem: function removeThisItem (item) {
this.todos.splice(this.todos.indexOf(item))
}
}
})
and the HTML
<div id="app">
<input type="text" name="" v-model="message">
<button type="button" name="button" v-on:click="addTodoItem">Add Todo Item</button>
<ul>
<todo-item
v-for="todo in todos"
v-bind:todotext="todo"
v-bind:key="todo.id">
</todo-item>
</ul>
</div>
However I get the error
invalid expression: Unexpected token '{' in removeThisItem({{todotext}})
Is there a way to pass the prop as an argument inside this function inside this template to be able to delete this list item?
Edit: Here is the JSFiddle: https://jsfiddle.net/f6sn52w8/
Thanks!
Well, trying to solve the issue in your jsfiddle I got the error
[Vue warn]: Property or method "outerHTML" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <TodoItem>)
Anyway, I figure it out what is happening with your code and why it isn't working.
You have the parent component where you are using your todo-item component:
<!-- parent component -->
<div id="app">
<input type="text" name="" v-model="message">
<button type="button" name="button" v-on:click="addTodoItem">
Add Todo Item
</button>
<ul>
<todo-item
v-for="todo in todos"
v-bind:todotext="todo"
v-bind:key="todo.id">
</todo-item>
</ul>
</div>
The method removeThisItem is declared in this component, so it isn't available in the child component <todo-item>, that's why you see the error in the console.
So the way to handle the click to remove the item is by listening for an event in the parent component and emitting the event from the child component:
Note about shorthand: v-bind:todotext="todo" is the same as :todotext="todo", and v-on:click is the same as #click
<!-- parent component -->
<div id="app">
<input type="text" name="" v-model="message">
<button type="button" name="button" v-on:click="addTodoItem">
Add Todo Item
</button>
<ul>
<todo-item
v-for="todo in todos"
:todotext="todo"
:key="todo.id"
#removeItem="removeThisItem"> <!-- listen for the removeItem event and run the removeThisItem method when it's triggered -->
</todo-item>
</ul>
</div>
Now the child component template must be updated:
Vue.component("todo-item", {
props: ["todotext"],
template:
`<li>
{{todotext.text}}
<button #click="$emit('removeItem', todotext)">X</button>
</li>`,
})
The todo-item component will emit the event removeItem when the button is clicked, and will send todotext prop as parameter to the function that will run on the parent (removeThisItem).
An alternative way to explain better this behavior:
Vue.component("todo-item", {
props: ["todotext"],
template:
`<li>
{{todotext.text}}
<button #click="emitEventRemoveItem">X</button>
</li>`,
methods: {
emitEventRemoveItem() {
// this.$emit will emit an event to the parent
// the first parameter is the event name, the second parameter
// is the argument that is expected in the parent method that
// will run when the event is triggered, removeThisItem in this case
this.$emit('removeItem', this.todotext);
}
}
})
Try to run this in your editor, in jsfiddle I got an error. Anyway, the issue is that you're trying to run a method that is declared in the parent component from the child component.
Let me know if it works or if you get any error.

v-show not working with props

I am trying to hide or show button using props.
Here is the code
View (Blade)
<product-form-component savebutton="false" updatebutton="false"></product-form-component>
Component template
<template>
<div class="form-actions text-right col-md-12">
<button v-show="showsavebutton" class="btn btn-primary">Save</button>
<button v-show="updatemode && showupdatebutton" class="btn btn- primary">Update</button>
</div>
</template>
Javascript
export default {
props: ['showupdatebutton', 'showsavebutton', 'modalid']
}
Two points:
The props you are passing don't work the way you think they do; and
You have to create data variables (or props) in the component with the names you are using in the v-show.
Passing props
When you pass like:
<product-form-component savebutton="false" updatebutton="false"></product-form-component>
inside the component, the savebutton and updatebutton properties will be strings. In the example above, they won't be the boolean false, they will be the string "false".
To bind them to different values, use v-bind:propname or its shorthand :propname:
<product-form-component :savebutton="false" :updatebutton="false"></product-form-component>
That way, inside the component, those properties will really have the value false.
Variables inside component and v-show
The variables you use in the v-shows:
<button v-show="showsavebutton" ...
<button v-show="updatemode && showupdatebutton" ...
Don't exist in your component. You have to create data variables (or props) in the component with the names you are using in the v-show.
Considering you already have some props declared, here's an example of declaring those v-show variables in the data() using the props as initial value:
Vue.component('product-form-component', {
template: "#pfc",
props: ['updatebutton', 'savebutton', 'modalid'],
data() {
return {
updatemode: this.updatebutton, // initialized using props
showupdatebutton: this.updatebutton,
showsavebutton: this.savebutton
}
}
})
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
}
})
<script src="https://unpkg.com/vue"></script>
<template id="pfc">
<div class="form-actions text-right col-md-12">
<button v-show="showsavebutton" class="btn btn-primary">Save</button>
<button v-show="updatemode && showupdatebutton" class="btn btn- primary">Update</button>
</div>
</template>
<div id="app">
<p>{{ message }}</p>
<product-form-component :savebutton="true" :updatebutton="true"></product-form-component>
</div>
Props as passed down to child with the bind syntax :, so in your case you forgot to add it:
try:
<product-form-component :savebutton="false" :updatebutton="false"></product-form-component>

Display value emitted from Vue component

I created two separated Vue components and I able to emit a message thru a bus.
How can I render/display the message in the component that receives the message.
Example of the Vue component that receives the message:
<template>
<div v-model="cars">
Car model: {{ model }}
<input type="button" #click="display" value="example" />
</div>
</template>
<script>
export default {
data() {
return {
cars: null
}
},
mounted() {
bus.$on('CARS_LOADED', (cars) => {
this.cars = cars;
});
},
methods: {
display()
{
console.log(this.cars);
}
}
}
</script>
I can successfully emit and received the message, however the car model is not updated. I checked the message received and it contains the "model" key with a right value.
I cannot see any error in the Vue console and however if I replace "{{ model }}" by "{{ cars }}" I can see the full message object updated.
I am using Vue 2.x.
Update:
I enclose an example:
https://jsfiddle.net/kvzvxk4f/1/
As you can see in the example I cannot render an specific field from the object, however I can render the object as string.
I think that you are misunderstanding some parts of the vue syntax.
How to access properties of an object:
You just need to write {{ car.model }} to access a property of an object.
How to iterate through an array in a template:
If you want to display all the cars in your template, you should write:
<div v-for="car in cars">
{{ car }}
</div>
As you see, the v-for directive allows you to iterate through an array.
What is v-model?
v-model is used to bind a variable to an input or a component.
<template>
<div>
<input type="text" v-model="foo" />
</div>
</template>
<script>
export default {
data () {
return {
foo: 'bar'
}
}
}
</script>
In that case, the foo property will be bound to the input text.
Last point:
In your case, to make it work, you also need to create a root element for your template, because a template can't have multiple root elements:
<template>
<div>
<div v-for="car in cars">
{{ car }}
</div>
</div>
</div>
I found the answer.
I just have to type property separated by ".". Like for example {{cars.model}}.
<template id="compo2">
<div>
<div>
{{ field.name }}
</div>
<div>
Received: {{ field }}
</div>
</div>
</template>
Example:
https://jsfiddle.net/zuhb7s8q/3/