How to execute a method of a nested component - vue.js

I have the component with form and I have the nested component with input['file']. Nested component have a method 'removeFile'. How can I execute this method when submit form?
// form
<form>
<input type="text">
<file-input></file-input>
</form>
// component "file-input"
<script>
export default {
methods: {
removeFile() {
// ***
}
}
};
</script>
<template>
<div>
<label>
<div>
<span #click="removeFile"></span>
</div>
<input type="file">
</label>
</div>
</template>

In most cases you want to avoid doing this if possible. Data should flow down and events should be emitted up.
That being said you can access by adding a ref to the child. In child you add a
<Child ref='foo'></>
Then in your parent you access the component vie 'this.$refs.foo' which will have all the normal method and data stuff on the child.
https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs

Related

Is it possible to use a prop as a v-model value?

Is it possible to use the value of a prop as the input's v-model?
I normally do the following when creating an input:
<template>
<form>
<input v-model="form.email" type="email"/>
</form>
</template>
<script>
export default {
data() {
return {
form: {
email: '',
}
}
}
}
</script>
But now I'm trying to achieve the following where this.myProp is used within the v-model without being displayed as a string on the input:
<template>
<form>
<input v-model="this.myProp" type="email"/>
</form>
</template>
<script>
export default {
props: ['myProp'] // myProp = form.email for example (to be handled in a parent component)
}
</script>
Yes, but while using it in parent component. In child component you need to extract value and #input instead of using v-model (v-model is shortcut for value="" and #input) Here is an example of input with label, error and hint in Vue 3 composition API.
BaseInput.vue
<template>
<div class="flex flex-col">
<label>{{ label }}</label>
<input v-bind="$attrs" :placeholder="label" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
<span v-for="item of errors" class="text-red-400">{{ item.value }}</span>
<span v-if="hint" class="text-sm">{{ hint }}</span>
</div>
</template>
<script setup>
defineProps({ label: String, modelValue: String | Number, errors: Array, hint: String })
defineEmits(['update:modelValue'])
</script>
Using v-bind="$attrs" you target where attributes like type="email" need to be applied in child component. If you don't do it, it will be added to the top level DOM element. In above scenario <div>.
ParentComponent.vue
<BaseInput type="email" v-model="formData.email" :label="Email" :errors="formErrors.email"/>

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.

How expose child element v-model as the vue component v-model

I Was using a simple text area in my vue component.:
<input v-model="someRootProperty"/>
I would like to encapsulate this input inside another component, for instance
<template>
<div>
<div>...</div>
<input v-model="???"/>
</div>
</template>
I would like to use
<my-component v-model="someRootProperty" />
instead and them bypass this to the input inside the component.
What should I do inside the component to expose its input v-model as the component v-model?
<input v-model="someRootProperty"/>
Is the same as (syntactic sugar):
<input :value="someRootProperty" #input="someRootProperty = $event.target.value"/>
That means that you could accept value as a prop in the component and emit input to achieve the same thing.
MyComponent.vue
<template>
<input :value="value" #input="$emit('input', $event.target.value)>
</template>
<script>
export default {
props: ['value']
}
And then use it like this.
<MyComponent v-model="someRootProperty"/>

Vue get child component value field

I have a vue component to input user address that I need to use twice on same page.
Below my parent containing the two components:
<h3>My Address</h3>
<adress :user="user" ref="userAddress"></adress>
<h3>Adress to delivery</h3>
<adress :user="user" ref="deliveryAddress"></adress>
<button type="submit" class="btn btn-primary pull-right" #click="ConfirmAdress()">Confirm</button>
Here is the vue configurations:
<script>
import AddressComponent from '../components/Address'
export default {
name: 'Adress',
computed: {
user () {
return this.$store.state.user
}
},
components: {
'adress': AddressComponent
}
}
</script>
I send the current user adress to te components so when the component is loaded the fields are filled with current information.
Here is my "Adress" component simplified
<template>
<div>
<div class="form-row">
<div class="form-group col-md-7">
<label for="inputAddress">Adress</label>
<input type="text" class="form-control" id="inputAddress" placeholder="Street, Aveneu" v-model="painelistaToLoad.addressName">
</div>
</div>
</div>
</template>
<script>
export default {
props: ['user']
}
</script>
The goal is to get the values of the filled fields in the two components in the parent. The problem is that when the user changes any input field in the "userAdress" component, the "deliveryAdress" component is updated automatically with the same information. This is my first step.
After the help I received from #Mat J, I needed to use the lodash _cloneDeep. That's because the vuex state is copied and/or passed by reference, so any changes I made to one of your copies were all changed. Now with _.cloneDeep all copies are vuex state independent, so I can change values inside my components

Do I always need to wrap the component content in a div (or similar)?

I'm creating single file components in Vue2, and I'm including a child component:
Parent Component:
<template>
<div>
<my-component-2>
</my-component-2>
</div>
</template>
<script>
....
</script>
Child component (my-component-2):
<template>
<my-component-3>
</my-component-3>
</template>
<script>
....
</script>
Grandchild component (my-component-3):
<template>
<div v-for="(item, index) in items">
</div>
</template>
<script>
...
</script>
But my-component-3 is not "rendered", however if I wrap <my-component-3> in a div (like in the parent component calling my-component-2), then it works.
Is there a way to call a child component without wrapping it in any html tag?
The <template> of a component can only have one direct child node.
Since your my-component-3 component's root element used a v-for, it could not render, since it would have multiple child nodes.
You never need to wrap a component in any element when using it in another component's template.
Example Multiple Elements
<template>
<template v-if="anyKey">
<div> DIV 1 Content </div>
<div> DIV 2 Content </div>
</template>
</template>
export default {
name: 'MyComponent',
data() {
return {
anyKey: "Any Value"
}
},
// ...
}
HTML will render without wrapping element
<div> DIV 1 Content </div>
<div> DIV 2 Content </div>