Vue: Event Methods- Confusion - events

I have a parent Vue which enables or disables "edit" mode. In non-edit mode all components are read only.
I've implemented this via a data object and all works fine.
I've split out some of the components in child components.
From the parent an $emit message is sent with the new edit mode state:
methods: {
toggleMode () {
this.editMode = !this.editMode
this.$emit('edit-mode-change', this.editMode)
}
Using Vue DevTools I can see the message is emitted.
However, I can't seem to receive it in my child component!I've looked a the docs, but none of the examples match this case. This is what I have currently (in the child component):
methods: {
onEditModeChange: function (mode) {
console.log('mode is', mode)
this.editMode = mode
}
Also tried:
events: {
onEditModeChange: function (mode) {
console.log('mode is', mode)
this.editMode = mode
}
Plus I'm getting an browser console error as follows:
[Vue warn]: Invalid handler for event "edit-mode-change": got false
(found in <Dimensions> at /home/anthony/Projects/Towers-Vue/src/components/assets/Dimensions.vue)
I'm sure I'm doing something basic wrong, but the docs don't reference the events: {} block, yet I've seen it on other code. Nor does it show how to implement a listener.
Thanks for taking the time to view this post, if you can point me in the right direction, it's much appreciated.

In Vue 2, events only flow laterally or up, not down.
What you really want is to simply pass a prop into your components.
In the parent JS:
toggleMode () {
this.editMode = ! this.editMode;
}
In the parent template:
<child-component-1 :editMode="editMode"></child-component-1>
...same for others...
Then simply accept editMode as a prop in each of your child components:
{
props: ['editMode']
}
You can now use editMode within your child's template. It'll track the parent's editMode, so no need for manual events/watchers.

The way vue2 works is by having a one-direction flow of the data, from parent to child, so in your parent component you can have
<template>
<child-component :isEditing="editMode"></child-component>
</template>
<script>
export default {
methods: {
toggleMode () {
this.editMode = !this.editMode
this.$emit('edit-mode-change', this.editMode)
}
}
}
and in child component you use props to get the data
Vue.component('child-component', {
props: ['isEditing'],
template: '<span>edit mode: {{ isEditing }}</span>'
})
we have cover the edit mode for the child. now if you want to send data from child to parent, then child needs to "emit" a signal to the parent, as props are "read only"
in child component you do at any point
someMethod() {
this.$emit('editDone', dataEdited);
}
and in your parent component you "intercept" the message using on:
<template>
<child-component
:isEditing="editMode"
#editDone="someParentMethod"></child-component>
</template>
Greetings!

Related

Laravel 9: Problem emitting event from child component to parent

In my Laravel 9 project, I have a parent component, whose template includes a child component.
template>
....
....
<div v-if=item.is_visible class="col-4">
<note-details-modal-component v-bind:is_visible=item.is_visible :note_id= item.id>
</note-details-modal-component>
</div>
On clicking a button in the parent component, I set is_visible to true that renders the child component through v-if. In the child component, when I press a button, it calls a method that emits an event to the parent.
closeNow: function() {
console.log('closeNow');
// this.$parent.$emit('close_note',false);
this.$emit('close_note',false);
} ,
In the parent component, I have a method.
close_note(value)
{
console.log('In parent');
this.is_visible = ! this.is_visible;
},
When I click the close button in the child component, it calls CloseNow() in the child component, and I see that in the console.log. However, that event does not emit to the parent. I have tried all suggestions that I could find on the web. Also, I do not see any errors in the Dev console.
Could someone please tell me what's wrong in my code that is preventing the event from emitting from the child component to parent?
Thanks in advance.
The thing is that nothing refer to the emit you made. If you have this:
closeNow: function() {
console.log('closeNow');
this.$emit('close_note',false);
}
You should mention the close_note when you call the child component. It should be something like that:
<note-details-modal-component v-bind:is_visible=item.is_visible :note_id= item.id #theEmitName="theFunctionYoucall">
</note-details-modal-component>
where theEmitName is close_note and the function you call has the same name. This medium can be usefull : https://medium.com/js-dojo/component-communication-in-vue-js-ca8b591d7efa

Vue not updating computed property based on a method, but the same method returns correct value when called directly

I have a simple button component for submitting forms and it should give a bit of feedback when clicked on it: show a spinner and disable the button while the form is being submitted and then also keep it disabled if there are any errors returned from the server until the errors are cleared (errors are cleared by changing the input of fields that have them).
Here's the template of the component:
<template>
<button type="submit" :disabled="disabled">
<loading-spinner v-if="submitting"></loading-spinner>
<slot>Submit</slot>
</button>
</template>
<script>
export default {
props: {
form: {
type: Object,
required: true
}
},
created() {
// Globally available event handler which listens for events
// emitted by Form object - it works fine
Event.$on('submitting', () => this.submitting = true);
Event.$on('submitted', () => this.submitting = false);
},
data() {
return {
submitting: false,
}
},
computed: {
// Here is the problem: this.form.errors.any() doesn't get updated in the
// computed prop even when errors are cleared. This same method, however,
// returns correct result when called directly via a test button
disabled() {
return this.submitting || this.form.errors.any()
},
// doesn't get updated either - apparently
// errors() {
// return this.form.errors.any()
// }
},
}
</script>
The button gets Form object passed to so it can also have access to Errors object which has some convenient methods to manage the errors. The Form object that is passed as prop is reactive inside the submit-button component and its fields get updated in Vue Devtools as I type into the form (on parent component), but the computed prop (disabled or errors) that relies upon it doesn't get updated.
When I submit the form with wrong data, I get errors, so form.errors.any() returns true. However, once errors are returned the first time, the button turns disabled and it "freezes" like that (form.errors.any() keeps returning true even when it isn't).
I know computed props are cached, but by definition they should update when their dependency gets updated (in this case the computed property disabled should get updated because this.form.errors.any() is updated). However, it seems that submit-button component "ignores" the update of this.form.errors.any() in its computed property (it keeps returning true once errors are present and it never gets updated). The same thing happens if I make a same computed property on parent component.
Additional info
When I manually check its value (e.g. via a button that calls form.errors.any()), it logs the correct value to the console every time, but it doesn't get updated in my computed property.
The any() method on Errors object - nothing fancy here
any() {
return Object.keys(this.errors).length > 0;
}
The Form and Errors object both work fine: their methods return normal values when called directly (via a button click, for example) from any component and they dispatch events as expected, they just don't react on a computed prop.
Workaround
The solution for me was to use a method instead of computed property, but it's a bit too verbose and I'd mostly like to know why can't I use more elegant computed property in this case?
<template>
<button type="submit" :disabled="submitting || anyErrors()">
<loading-spinner v-if="submitting"></loading-spinner>
<slot>Submit</slot>
</button>
</template>
<script>
export default {
...
methods: {
anyErrors() {
return this.form.errors.any()
}
}
}
</script>
You need to place the form.errors property on your props for form in order to have it reactive out of the box. Otherwise, when you add errors, you have to use the Vue.set() method in order to make it reactive.
So your props object should look like:
props: {
form: {
type: Object,
required: true,
errors: {},
}
},
More info:
https://v2.vuejs.org/v2/guide/reactivity.html
(edited to make errors an object, as indicated in the question, instead of an array)

Listen to events from parent component in child and execute child’s method in vue without hub

There seems to be a lot of discussion around this topic such as Stackoverflow answer using hub, Stackoverflow answer using refs, so I really like to ask experts to provide for once a clear concise answer to this question. If the answer is also just not possible please state that!
Here is the scenario:
There are two components, a parent and a child
<Parent> // There is a button here that can be clicked to emit an event using 'this.$emit()'
<Child></Child> // The child listens and once hears the event, it does something
</Parent>
What to be achieved?
Clicking the button in the Parent emits a certain event, the child will be constantly listening and once it hears the event it executes an action, such as calling a method of its own.
What is out there about this so far?
Using a hub, in Vue Hub it is clearly stated this is for Non
Parent-Child Communication, so what is the point in using it for a
parent-child communication?
Using Refs, which is given as an end solution when it is not possible to use props and events. So why it is not possible with events at first place?
My own thought
It seems to me the firing of an event and listening to it is only possible from child to parent, basically one way communication. The parent is able to emit an event but child component(s) are not able to capture the event. Why? I tried this and didn’t work:
In the parent component I have (triggered by clicking a button in the parent component):
methods: {
generateCharts: function () {
this.$emit('generate-charts')
console.log('charts generated')
}
In the child component I have:
mounted () {
this.parent.$on('generate-charts', function () {
console.log('event captured') // Here nothing gets logged to the console
})
}
Update
Just came across this answer Vue $emit.
Apparently this is not possible at all with Vue.
At first instance it seems it is a deficiency because I have been in several situations where I needed to fire an event from parent and listen to it in the child.
I can imagine there must be a reason why this is not possible with Vue, it is probably a design consideration, Vue experts explaining why this is the case, and what is the better design approach to solve in general a scenario to pass events from parent to child, would be very appreciated.
The answer is to use props and react to changes to those props. It is a little confusing to get used to at first because it seems like a lot of code to to do something simple but as your application gets more complex, the one way data flow enforced by the use of props really helps with debugging and reasoning about what the code is trying to accomplish.
For instance, a modal. Your parent sets showChildModal = true with a button click, this value is passed to the child as a prop. The child is watching for changes to the prop, when it sees it set to true it opens the modal. Finally, when the modal is closed, the child $emit('close') which the parent is watching for, and when it sees that it sets showChildModal = false
Vue.component('child', {
template: '#child',
props: ['showModal'],
name: 'child',
watch: {
showModal: function(show) {
if (show) {
window.setTimeout(() => {
if (window.confirm("Hi, I'm the child modal")) {
this.$emit('close');
} else {
this.$emit('close');
}
}, 100)
}
}
}
})
var vm = new Vue({
el: '#el',
data() {
return {
showChildModal: false
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="el">
Parent
<p>
<button #click="showChildModal = true">Click to show child modal</button>
</p>
<hr>
<p>
<child :show-modal="showChildModal" v-on:close="showChildModal = false"> </child>
</p>
</div>
<script type="x-template" id="child">
<div>
Child
<p>
modal open? {{ showModal }}
</p>
</div>
</script>

Polymer catch all events from child element

I am currently working on making lazy-loading possible together with "more-routing" in Polymer. I already got a first working implementation of lazy loading (inspired by app-router that unfortunately does not support Polymer 1.0 completely), but I have no idea how I can pass all events from the imported child element up to my lazy-loading element.
So, in my example I want the event "hi" passed from page-login to lazy-loader and from lazy-loader upwards to the element containing lazy-loader - but without having knowledge of that specific event in lazy-loader. So I need something like "catch all events fired by child element".
That's what I have :
page-login:
ready:function() {
this.fire("hi");
},
using lazy-loader:
<lazy-loader on-hi="hi" href="../pages/page-login.html" id="login" route="login"></lazy-loader>
calling load :
document.getElementById("login").load()
Lazy-loader (reduced version):
<dom-module id="lazy-loader">
<template>
<span id="content">Loading...</span>
</template>
<script>
window.lazyLoaded = window.lazyLoaded || [];
Polymer({
is: "lazy-loader",
properties: {
href:{
type:String,
value:""
},
element:{
type:String,
value:""
}
},
load:function(finishFunc) {
if(window.lazyLoaded.indexOf(this.href)===-1) {
var that = this;
this.importHref(this.href, function(e) {
window.lazyLoaded.push(that.href);
if(!that.element) {
that.element = that.href.split('/').reverse()[0];
that.element = that.element.split(".")[0];
}
var customElement = document.createElement(that.element);
// here I need something like "customElement.on("anyEvent")"
// => that.fire("anyEvent")
Polymer.dom(that.$.content).innerHTML = "Loading done.";
Polymer.dom(that.$.content).appendChild(customElement);
Polymer.dom.flush();
that.fire("loaded");
});
}
}
});
</script>
It seems like I have to revoke my question - If I fire the event in the attached-handler, not in ready, it gets passed through automatically - at least in the current version of Polymer. Not sure if this is intended behavior, but for now the problem is solved. An answer still would be interesting, maybe for other cases (like event logging)

Encapsulation with React child components

How should one access state (just state, not the React State) of child components in React?
I've built a small React UI. In it, at one point, I have a Component displaying a list of selected options and a button to allow them to be edited. Clicking the button opens a Modal with a bunch of checkboxes in, one for each option. The Modal is it's own React component. The top level component showing the selected options and the button to edit them owns the state, the Modal renders with props instead. Once the Modal is dismissed I want to get the state of the checkboxes to update the state of the parent object. I am doing this by using refs to call a function on the child object 'getSelectedOptions' which returns some JSON for me identifying those options selected. So when the Modal is selected it calls a callback function passed in from the parent which then asks the Modal for the new set of options selected.
Here's a simplified version of my code
OptionsChooser = React.createClass({
//function passed to Modal, called when user "OK's" their new selection
optionsSelected: function() {
var optsSelected = this.refs.modal.getOptionsSelected();
//setState locally and save to server...
},
render: function() {
return (
<UneditableOptions />
<button onClick={this.showModal}>Select options</button>
<div>
<Modal
ref="modal"
options={this.state.options}
optionsSelected={this.optionsSelected}
/>
</div>
);
}
});
Modal = React.createClass({
getOptionsSelected: function() {
return $(React.findDOMNode(this.refs.optionsselector))
.find('input[type="checkbox"]:checked').map(function(i, input){
return {
normalisedName: input.value
};
}
);
},
render: function() {
return (
//Modal with list of checkboxes, dismissing calls optionsSelected function passed in
);
}
});
This keeps the implementation details of the UI of the Modal hidden from the parent, which seems to me to be a good coding practice. I have however been advised that using refs in this manner may be incorrect and I should be passing state around somehow else, or indeed having the parent component access the checkboxes itself. I'm still relatively new to React so was wondering if there is a better approach in this situation?
Yeah, you don't want to use refs like this really. Instead, one way would be to pass a callback to the Modal:
OptionsChooser = React.createClass({
onOptionSelect: function(data) {
},
render: function() {
return <Modal onClose={this.onOptionSelect} />
}
});
Modal = React.createClass({
onClose: function() {
var selectedOptions = this.state.selectedOptions;
this.props.onClose(selectedOptions);
},
render: function() {
return ();
}
});
I.e., the child calls a function that is passed in via props. Also the way you're getting the selected options looks over-fussy. Instead you could have a function that runs when the checkboxes are ticked and store the selections in the Modal state.
Another solution to this problem could be to use the Flux pattern, where your child component fires off an action with data and relays it to a store, which your top-level component would listen to. It's a bit out of scope of this question though.

Resources