Running a method on click from a child component slot? - vuejs2

I have a third part component that accepts a slot, inside this slot I want to place an input like so:
<input type="text" #click="this.run">
The issue I am having is this logs:
Invalid handler for event "click": got undefined
On my parent component where I use the third party component I have the method set up:
methods: {
run() {
console.log('run');
},
},
Where am I going wrong?

The template will get compile into a render method.
So you should not use this as it will be implicit.
<input type="text" #click="run">
Should work fine

Related

vue 2 components, one for display, one for changing?

I would like to have two components, one for displaying a value, and one for changing it with a text field. I can't get this to work? Is there another way of doing this?
I get this error message:
"Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "forpris""
Vue.component('prislapp-forpris', {
props: ['forpris'],
template: '<div class="prislappForpris">[[ forpris ]],-</div>',
delimiters: ['[[',']]']
});
Vue.component('input-forpris', {
props: ['forpris'],
template: '<input type="text" v-model="forpris" />'
});
var app = new Vue({
el: '.previewPage',
data: {
lapp: {
id: 1,
forpris: 30595
}
}
});
It's all about v-model directly mutating the forpris prop. As the warning states, you should avoid to mutate a prop from a component.
 Rationale behind the warning
The reason is that allowing child component to modify props that belong to their parents make programs more error prone and difficult to reason about.
Instead, the idea behind Vue and other component oriented architectures and frameworks is that child components emit events to their parents, and then the parents change their own state, which in turn modify the child component via events from their children.
This ensures that the component passing down the props have full control of the state and may, or may not, allow the desired state changes that come via props.
How to fix your code to avoid the warning
v-model is syntax sugar over a :value and an #input on the input element. A really good read to understand how v-model innerly works is this article.
What you should do, is to drop v-model on the input for this:
template: '<input type="text" :value="forpris" #input="$emit('input', $event)" />'
This will set forpris as the value of the input (as v-model was already doing), but, instead of automatically modifying it, now the component will emit an input event when the user writes in the input.
So you now need to listen for this event in the parent and react accordingly. Now from your code is not absolutely clear who is rendering the two component, I guess the rendering comes from the .previewPage element in the Vue template, so the Vue instance is the parent component here.
You don't show the html of that template, but I guess it is something like the following:
<div class="previewPage">
<prislapp-forpris :forpriss="lapp.forpris" />
<input-forpris :forpriss="lapp.forpris" />
</div>
You now should listen to the #input event in the input-forpriss component:
<div class="previewPage">
<prislapp-forpris :forpriss="lapp.forpris" />
<input-forpris :forpriss="lapp.forpris" #input="handleInput" />
</div>
So, whenever we receive an #input event, we call the handleInput method. We also need to add such method to the Vue instance:
var app = new Vue({
el: '.previewPage',
data: {
lapp: {
id: 1,
forpris: 30595
}
},
methods: {
handleInput(value){
console.log(value); // now I'm not 100% sure if this
// is the value or a DOM event, better check
this.lapp.forpriss = value;
},
}
});

How can I use `console.log` or `console.error` in a vue template?

I have a vue component with
<form #keydown="console.error($event.target.name);">
gives
app.js:47961 [Vue warn]: Property or method "console" is not defined
on the instance but referenced during render.
window.console doesn't work either
What is the proper way to use console and window in a template to debug?
Simplest way of providing global objects to the template is to place them in computed, like this:
console: () => console. Same goes for window,
computed: {
console: () => console,
window: () => window,
}
See it here.
If you want to run it inline instead of using a method, just add this to the form:
Codepen: https://codepen.io/x84733/pen/PaxKLQ?editors=1011
<form action="/" #keydown="this.console.log($event.target.name)">
First: <input type="text" name="fname"><br>
Second: <input type="text" name="fname2"><br>
</form>
But it'd be better to use a method instead of running functions inline, so you have more control over it:
<!-- Don't forget to remove the parenthesis -->
<form action="/" #keydown="debug">
First: <input type="text" name="fname"><br>
Second: <input type="text" name="fname2"><br>
</form>
...
methods: {
debug (event) {
console.log(event.target.name)
}
}
You can use $el.ownerDocument.defaultView.console.log() inside your template
Pro: Doesn't require any component changes
Con: Ugly
Also if you want to access console from {{ }} you can use global mixin:
Vue.mixin({
computed: {
console: () => console
}
})
If using Vue 3, do:
const app = createApp(App)
app.config.globalProperties.$log = console.log
If using Vue 2, do:
Vue.prototype.$log = console.log
Use $log inside template:
<h1>{{ $log(message) }}</h1>
To not interfere with the rendering, use $log with ?? (or || if using Vue 2, since ?? is not supported in the template):
<h1>{{ $log(message) ?? message }}</h1>
You can use this.console instead console or wrap call to console in a method, i am using eslint config with rule 'no-console': 'off'
You can use computed property or methods for this case.
If you need to code it as javascript in the Vue template. you have to define console in the data.
Please check the code below.
data(){
return {
selected :"a",
log : console.log
}
}
<span>{{log(selected)}}</span>
This will make functionality of console.log available, while resolving the template.
I'd make a getter for console template variable:
get console() { return window.console; }
For Vue 3, SFC Composition API, you have to define a function and call console or alert inside that function
<script setup>
import Child from "./Child.vue";
function notify(message) {
alert(message);
}
</script>
<template>
<Child #some-event="notify('child clicked')" />
</template>

Vue: computed vs data(), for vuex bindings?

(Note: I not am not asking about how to use watch).
I have this form template and want to bind it to some variables, for example objectvalue3, that are tracked in a Vuex store (note: different forms may be present on one page, so props.formname tells the form where to look).
<template>
<div>
Tracking formname_:{{formname_}}:
<form method="post" autocomplete="off">
<input type="text" name="objectvalue3" :value="objectvalue3" />
<input type="submit" class="btn btn-primary btn-success" value="Track" #click.prevent="write">
</form>
</div>
</template>
....
props: {
formname: {
type: String,
default: "main"
}
},
Tracking it in data does not work - i.e. the form does not get updated - it just keeps the value the vuex was initialized to :
data: function() {
return {
formname_: this.formname
,objectvalue3: this.$store.state.tracker[this.formname].objectvalue3
},
But using computed works.
computed: {
objectvalue3: function() {
return this.$store.state.tracker[this.formname].objectvalue3
}
I know I have to use computed when I need to do calculations. But there is not real calculation going on here. Unless, could it be the hash lookup via this.formname which tells the form which tracker attribute to look at that is causing a straight data to fail? Is this specific to vuex?
Try this instead:
data: function() {
return {
formname_: this.formname,
tracker: this.$store.state.tracker[this.formname]
},
Then:
<input type="text" name="objectvalue3" :value="tracker.objectvalue3" />
This should work because the data's tracker object is pointing to the one in the store, then whenever the value changes there, it'll also change in the tracker object.
computed works because it listens to changes in the variables used within it, while data doesn't because it applies the first value when executed and doesn't track changes.

Why v-on doen't work on Vue component while using it in outer template?

The situation
Let's say I have a component BaseButton which is just a simple wrapper arround a <button> element:
Vue.component('base-button', {
template: `
<button>
<slot />
</button>
`
});
When using the button, I'll want to bind handler to a click event on this button:
<base-button #click="handler">
Click me
</base-button>
Pen with above code
The problem
The above solution doesn't work. The handler is not fired at all.
Can someone explain me why exactly? I'm guessing the handler is bound to the element, before it gets replaced with the component template, but it's just a guess - I can't find anything about it in vue.js docs. In addition to that docs
state:
A non-prop attribute is an attribute that is passed to a component,
but does not have a corresponding prop defined.
While explicitly defined props are preferred for passing information
to a child component, authors of component libraries can’t always
foresee the contexts in which their components might be used. That’s
why components can accept arbitrary attributes, which are added to the
component’s root element.
#click (v-on:click) seems to me to be a non-prop attribute and according to the above text should get inherited. But it's not.
Prop solution
I know I can declare a prop and pass the handler inside the component (code below). Then it works as expected.
The problem with this solution for me is that I don't have a fine grain control over how the handler is declared. What if in one usage of BaseButton I'd like to use on #click
some of the event modifiers Vue.js exposes (e.g. .stop, .prevent, .capture)? I'd have to use another prop (like capture) and use v-if, but it'd get the component template very messy. If I leave the handler in the template, where I use it, I can modify the event declaration as I want in a clean and flexible way.
Vue.component('base-button', {
prop: {
clickHandler: {
type: Function,
required: true
}
},
template: `
<button>
<slot />
</button>
`
});
<base-button :click-handler="handler">
Click me
</base-button>
The v-on directive behaves differently when used on a normal / native DOM element or on a Vue custom element component, as stated in the API docs:
When used on a normal element, it listens to native DOM events only. When used on a custom element component, it listens to custom events emitted on that child component.
In your case you apply it on your custom <base-button> element component, therefore it will listen only to custom event, i.e. ones that you explicitly $emit on this component instance.
Native "click" events bubbling phase from your underlying <button> will not trigger your #click listener…
…unless you use the .native modifier:
.native - listen for a native event on the root element of component.
Vue.component('base-button', {
template: `
<button>
<slot />
</button>
`
});
new Vue({
el: '#app',
data: {
handler(event) {
console.log('submit from where the component was used');
//console.log(event);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<base-button #click.native="handler">
Click
</base-button>
</div>

Update parent model from child component Vue

I have a very small app that has a donation form. The form walks the user through the steps of filling in information. I have a main component, which is the form wrapper and the main Vue instance which holds all of the form data (model). All of the child components are steps within the donation process. Each child component has input fields that are to be filled out and those field will update the parent model so that I have all of the form data in the parent model when I submit the form. Here is how the components are put together:
<donation-form></donation-form> // Main/Parent component
Inside the donation-form component:
<template>
<form action="/" id="give">
<div id="inner-form-wrapper" :class="sliderClass">
<step1></step1>
<step2></step2>
<step3></step3>
</div>
<nav-buttons></nav-buttons>
</form>
</template>
Right now, I am setting the data from the inputs in each child component and then I have a watch method that is watching for fields to update and then I am pushing them to the $root by doing this...
watch: {
amount() {
this.$root.donation.amount = this.amount;
}
}
The problem is that one of my steps I have a lot of fields and I seem to be writing some repetitive code. Also, I'm sure this is not the best way to do this.
I tried passing the data as a prop to my child components but it seems that I cannot change the props in my child component.
What would be a better way to update the root instance, or even a parent instance besides add a watch to every value in my child components?
More examples
Here is my step2.vue file - step2 vue file
Here is my donation-form.vue file - donation-form vue file
You can use custom events to send the data back.
To work with custom events, your data should be in the parent component, and pass down to children as props:
<step1 :someValue="value" />
and now you want to receive updated data from child, so add an event to it:
<step1 :someValue="value" #update="onStep1Update" />
your child components will emit the event and pass data as arguments:
this.$emit('update', newData)
the parent component:
methods: {
onStep1Update (newData) {
this.value = newData
}
}
Here is a simple example with custom events:
http://codepen.io/CodinCat/pen/QdKKBa?editors=1010
And if all the step1, step2 and step3 contain tons of fields and data, you can just encapsulate these data in child components (if the parent component doesn't care about these row data).
So each child has its own data and bind with <input />
<input v-model="data1" />
<input v-model="data2" />
But the same, you will send the result data back via events.
const result = this.data1 * 10 + this.data2 * 5
this.$emit('update', result)
(again, if your application becomes more and more complex, vuex will be the solution.
Personally I prefer having a generic function for updating the parent, when working with forms, instead of writing a method for every child. To illustrate – a bit condensed – like this in the parent:
<template lang="pug">
child-component(:field="form.name" fieldname="name" #update="sync")
</template>
<script>
export default {
methods: {
sync: function(args) {
this.form[args.field] = args.value
}
}
}
</script>
And in the child component:
<template lang="pug">
input(#input="refresh($event.target.value)")
</template>
<script>
export default {
props: ['field', 'fieldname'],
methods: {
refresh: function(value) {
this.$emit('update', {'value': value, 'field': this.fieldname});
}
}
}
</script>
For your case you can use v-model like following:
<form action="/" id="give">
<div id="inner-form-wrapper" :class="sliderClass">
<step1 v-model="step1Var"></step1>
<step2 v-model="step2Var"></step2>
<step3 v-model="step3Var"></step3>
</div>
<nav-buttons></nav-buttons>
</form>
v-model is essentially syntax sugar for updating data on user input events.
<input v-model="something">
is just syntactic sugar for:
<input v-bind:value="something" v-on:input="something = $event.target.value">
You can pass a prop : value in the child components, and on change of input field call following which will change the step1Var variable.
this.$emit('input', opt)
You can have a look at this answer where you can see implementation of such component where a variable is passed thouugh v-model.