Can't emit from one child to another - vue.js

I have component, inside it I am doing emiting:
methods: {
sendClick(e)
{
bus.$emit('codechange', this.mycode);
console.log(this.selectedLanguage);
this.$parent.sendCode();
this.$parent.changeView();
}
}
In the parent component I am hadling data:
var app = new Vue({
el: '#app',
data: {
currentView: 'past-form',
mycode: ''
},
methods:
{
changeView()
{
console.log(this.mycode);
},
sendCode()
{
console.log("Code is sended to server");
this.currentView = 'view-form';
bus.$emit('codechange', this.mycode);
}
},
created()
{
bus.$on('codechange', function(mycode){
console.log("test"); // this works
this.mycode = mycode; // this works
}.bind(this));
}
})
Handling in parent work fine. But on clicking on sendCode() I want to send data to third component. The third component code:
Vue.component('view-form', {
template: `
<div class="ViewCodeContainer">
<div class="ViewCode">my code here</div>
<code> {{mycode}} </code>
<div class="ViewCodeMenu">my menu here</div>
</div>`,
data() {
return {
mycode: ''
}
},
created()
{
bus.$on('codechange', function(mycode){
console.log("hererere");
console.log(mycode);
this.mycode = mycode;
}.bind(this));
console.log("test");
}
})
But handling of code does not working. Block console.log("hererere"); is not executed. What I am doing wrong?

#Wostex is correct in this case. Essentially, your view-form component doesn't exist when the event is emitted. It doesn't exist until you change the view, which you are doing in the event handler. So there is no way for it to receive the event because your handler doesn't exist.
If your dynamic component is a child of the parent, just pass the code as a property.
<component :is="currentView" :mycode="mycode"></component>
And update your view-form component.
Vue.component('view-form', {
props:["mycode"],
template: `
<div class="ViewCodeContainer">
<div class="ViewCode">my code here</div>
<code> {{code}} </code>
<div class="ViewCodeMenu">my menu here</div>
</div>`,
data() {
return {
code: this.mycode
}
}
})
Here is a working example.

Related

VueJS Trying to capture enter press on child component before parent component handles enter

I have an auto complete input that when I press enter in this input, it is to emit its value to the parent component and then the parent submit action should be handled. However it appears that the parent is first receiving the enter key submiting the form and then the child component will finally emit the value meaning the data doesnt get updated until after it is needed.
I have an example code pen I made up
codepen
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #keyup.enter.capture="enterPressed" v-model="someData" />
</div>
`,
methods: {
enterPressed(){
this.$emit('updateData',this.someData)
console.log('CHILD: enter pressed')
}
}
});
Vue.component('parent', {
data () {
return {
lastGo: null,
parentData: "init"
}
},
template: `
<form v-on:submit.prevent="go">
<child #updateData="updateData"></child>
<button #click="go">Go</button>
<p>Parent data: <b>{{parentData}}</b></p>
<p>Last go: <b>{{lastGo}}</b></p>
</form>
`,
methods: {
updateData(data){
this.parentData = data;
},
go(){
this.lastGo = this.parentData;
console.log("go: "+this.parentData)
}
}
});
new Vue({
el: '#app'
});
I'm not sure how to resolve this, I feel maybe that my pattern just isn't going to work, is there a better way?
There is a way to work around is using #input event in child component
Vue.component('child', {
data () {
return {
someData: ""
}
},
template: `
<div>
<input #input="onInput" v-model="someData" />
</div>
`,
methods: {
onInput(){
console.log('CHILD: enter pressed')
this.$emit('updateData',this.someData)
}
}
});
Demo

How to build a bridge of events between different components in Vue?

I need to solve it
1) click mainMidLeft component
2) after clicked, to move slideLeftTop component
http://joxi.ru/ZrJBvERH1JVa8r
The problem I dont quite understand how to do this in right way..
Is it okay to create in mainMidLeft a method where I will do somethik like this:
move: () => {
document.querySelector(`.slideLeftTop`).style.position .....
}
The best practice is to use Vuex State manager with computed methods (getters) and watchers
I have made a working example for you on jsfiddle.
https://jsfiddle.net/n4e_m16/wujafg5e/4/
For more info on how vuex works please go to Here
Please let me know if you need more help :)
const store = new Vuex.Store({
state: {
mainMidLeftState: false,
},
getters: {
mainMidLeftState: state => state.mainMidLeftState,
},
mutations: {
toggleMainMidLeft: (state, payload) => {
state.mainMidLeftState = !state.mainMidLeftState
},
},
})
Vue.component('main-mid-left', {
data() {
return {
}
},
computed: {
mainMidLeftState() {
return this.$store.state.mainMidLeftState
},
},
methods: {
toggleMainMidLeft() {
this.$store.commit('toggleMainMidLeft')
// alert(this.mainMidLeftState)
},
}
})
Vue.component('slide-left-top', {
data() {
return {
}
},
computed: {
mainMidLeftState() {
return this.$store.state.mainMidLeftState
},
},
watch: {
mainMidLeftState: function(val) {
alert("YES, computed property changed and alert have been triggered by watcher in slide top left component")
}
},
methods: {
}
})
const app = new Vue({
el: '#app',
store,
})
div {
color: black;
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuex#3.0.1/dist/vuex.js"></script>
<div id="app">
<!-- inlining the template to make things easier to read - all of below is held on the component not the root -->
<main-mid-left inline-template>
<div>
<h4>
main mid left
</h4>
<button v-on:click="toggleMainMidLeft()">toggle Main Mid Left State</button>
<div v-show="mainMidLeftState == true">State is true</div>
<div v-show="mainMidLeftState == false">State is false</div>
</div>
</main-mid-left>
<slide-left-top inline-template>
<div>
<h4>
slide left top
</h4>
<div v-show="mainMidLeftState == true">State is true</div>
<div v-show="mainMidLeftState == false">State is false</div>
</div>
</slide-left-top>
</div>
If you don't want to use vuex, you can create a new Vue instance as an event bus (I believe this is mentioned somewhere in the Vue tutorial):
const EventBus = new Vue()
Import EventBus to where you need it and you can send an event by:
EventBus.$emit('event-name', data)
And add the following script in created() of your receiver component:
EventBus.$on('event-name', ($event) => {
// Do something
})
I hope this helps |´・ω・)ノ

Fetch data in component on initiation using parameters from Vuex store

I am new to Vue and am trying to build a simple movie app, fetching data from an API and rendering the results. I want to have an incremental search feature. I have an input field in my navbar and when the user types, I want to redirect from the dashboard view to the search results view. I am unsure of how to pass the query params from the navbar to the search results view.
Here is my App.vue component
<template>
<div id="app">
<Navbar></Navbar>
<router-view/>
</div>
</template>
<script>
import Navbar from './components/Navbar.vue'
export default {
name: 'App',
components: {
Navbar
},
}
</script>
And here is my navbar component where I have the input field
<template>
<nav class="navbar">
<h1 class="logo" v-on:click="goToHome">Movie App</h1>
<input class="search-input" v-on:keyup="showResults" v-model="query" type="text" placeholder="Search..."/>
</nav>
</template>
<script>
import router from '../router/index'
export default {
data: function () {
return {
query: this.query
}
},
methods: {
goToHome () {
router.push({name: 'Dashboard'})
},
showResults () {
//here on each key press I want to narrow my results in the SearchedMovies component
}
}
}
</script>
If I use router.push to the SearchedMovies component then I am only able to pass the query as a parameter once. I thought about using Vuex to store the query and then access it from the SearchedMovies component, but surely there is a better way of doing it?
I also read about using $emit but since my parent contains all the routes, I'm not sure how to go about this.
You don't need to redirect user anywhere. I've made a small demo to show how one might do it. I used this navbar component as you described and emit an event from it:
const movies = {
data: [
{
id: 0,
title: 'Eraserhead',
},
{
id: 1,
title: 'Erazerhead',
},
{
id: 2,
title: 'Videodrome',
},
{
id: 3,
title: 'Videobrome',
},
{
id: 4,
title: 'Cube',
},
]
};
Vue.component('navbar', {
template: '<input v-model="filter" #input="onInput" placeholder="search">',
data() {
return {
filter: '',
};
},
methods: {
onInput() {
this.$emit('filter', this.filter);
}
}
});
// this is just a request imitation.
// just waiting for a second until we get a response
// from the datasample
function request(title) {
return new Promise((fulfill) => {
toReturn = movies.data.filter(movie => movie.title.toLowerCase().indexOf(title.toLowerCase()) !== -1)
setTimeout(() => fulfill(toReturn), 1000);
});
}
new Vue({
el: '#app',
data: {
movies: undefined,
loading: false,
filter: '',
lastValue: '',
},
methods: {
filterList(payload) {
// a timeout to prevent
// instant request on every input interaction
this.lastValue = payload;
setTimeout(() => this.makeRequest(), 1000);
},
makeRequest() {
if (this.loading) {
return;
}
this.loading = true;
request(this.lastValue).then((response) => {
this.movies = response;
this.loading = false;
});
}
},
mounted() {
this.makeRequest('');
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<navbar v-on:filter="filterList"></navbar>
<ul v-if="!loading">
<li v-for="movie in movies" :key="movie.id">{{ movie.title }}</li>
</ul>
<p v-else>Loading...</p>
</div>
Also jsfiddle: https://jsfiddle.net/oniondomes/rsyys3rp/
If you have any problem to understand the code above let me know.
EDIT: Fixed some bugs and added a couple of comments
EDIT2(after the comment below):
Here's what you can do. Every time user inputs something inside a navbar you call a function:
// template
<navbar v-on:input-inside-nav-bar="atInputInsideNavBar"></navbar>
// script
methods: {
atInputInsideNavBar(userInput) {
this.$router.push({
path: '/filtred-items',
params: {
value: userInput
}
})
}
}
Then inside you 'searched movies' page component you can access this value so:
this.$route.params.value // returns userInput from root component

Passing in a prop and setting it as data

I'm trying to pass a prop from my drop down button component:
<template>
<div>
<p #click="toggleActive">Open Drop Down</p>
<drop-down :active="this.active"></drop-down>
</div>
</template>
<script>
export default {
data() {
return {
active: false,
}
},
methods: {
toggleActive() {
return this.active = ! this.active;
}
}
}
</script>
To my drop down component:
<template>
<div class="drop-down" v-if="this.passedActive">
<p #click="toggleActive">Close drop down</p>
....
<script>
export default {
props: ['active'],
data() {
return {
passedActive: this.active,
}
},
methods: {
toggleActive() {
return this.passedActive = ! this.passedActive;
}
}
}
</script>
The idea is that I can activate the drop down component from it's parent, and then inside the drop down component I can modify this prop and deactivate the drop down - as if someone is pressing an 'x' inside the component.
I've checked the docs and this does appear to be the correct way to do it, but for some reason it's not working.
The code below works. As noted in the comments under your question, passedActive is initialized once. The parent controls the initial state (only), and the child itself controls any subsequent state. If you start with it false, it never gets to become true, because the controller is never displayed.
That is a design flaw: there should be one data item that controls it, not two. The child component should rely on its prop, and its toggle function should emit an event that the parent handles.
new Vue({
el: '#app',
data: {
active: true
},
methods: {
toggleActive() {
console.log("Toggling");
this.active = !this.active;
}
},
components: {
dropDown: {
props: ['active'],
data() {
return {
passedActive: this.active,
}
},
methods: {
toggleActive() {
return this.passedActive = !this.passedActive;
}
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<p #click="toggleActive">Open Drop Down {{active}}</p>
<drop-down :active="active" inline-template>
<div class="drop-down" v-if="this.passedActive">
<p #click="toggleActive">Close drop down</p>
</div>
</drop-down>
</div>

Vuejs DOM doesn't update after fetching data

I have bound an array events to a component tag <scheduler> containing events to fill in a scheduler app (dhtmlx scheduler). However, the DOM doesn't seeem to refresh itself when data is retrieved by the getEvents methods triggered when vue instance is created.
There is 2 vue files I work with: App.vue containing the main app component and the Scheduler.vue file containing the scheduler component.
The thing is that when I modify something in the Scheduler.vue file and save it, it correctly take the updated events array into account.
Scheduler parse the data in the events prop when DOM is mounted in scheduler component.
Therefore is there something I can do to get the updated array ?
Here is the App.vue:
<template>
<div id="app">
<div class="container">
<scheduler v-bind:events="events"></scheduler>
</div>
</div>
</template>
<script>
import Scheduler from './components/Scheduler.vue';
import auth from './components/auth/index'
import data from './components/data/index'
export default {
name: 'app',
components: {
Scheduler
},
data() {
return {
events: []
}
},
created() {
this.getEvents();
},
watch: {
events: function(value) {
console.log('updated');
}
},
methods: {
async getEvents() {
try {
const token = await auth.getToken(this);
const thattoken = await auth.getThatToken(this, token);
await data.getOgustData(this, token, '/calendar/events', 307310564, this.events);
} catch (e) {
console.log(e);
}
},
}
}
</script>
Here is Scheduler.vue:
<template lang="html">
<div ref="scheduler_here" class="dhx_cal_container" style='width:100%; height:700px;'>
<div class="dhx_cal_navline">
<div class="dhx_cal_prev_button"> </div>
<div class="dhx_cal_next_button"> </div>
<div class="dhx_cal_today_button"></div>
<div class="dhx_cal_date"></div>
<div class="dhx_cal_tab" name="day_tab" style="right:204px;"></div>
<div class="dhx_cal_tab" name="week_tab" style="right:140px;"></div>
<div class="dhx_cal_tab" name="month_tab" style="right:76px;"></div>
</div>
<div class="dhx_cal_header"></div>
<div class="dhx_cal_data"></div>
</div>
</template>
<script>
import 'dhtmlx-scheduler'
import 'dhtmlx-scheduler/codebase/locale/locale_fr';
import 'dhtmlx-scheduler/codebase/ext/dhtmlxscheduler_readonly.js';
export default {
name: 'scheduler',
props: {
events: {
type: Array,
default () {
return [{
id: '',
text: '',
start_date: '',
end_date: '',
}]
}
}
},
mounted() {
scheduler.config.xml_date = '%Y-%m-%d %H:%i';
// disable left buttons on lightbox
scheduler.config.buttons_left = [];
// enable cancel button on lightbox's right wing
scheduler.config.buttons_right = ['dhx_cancel_btn'];
// changing cancel button label
scheduler.locale.labels['icon_cancel'] = 'Fermer';
// hide lightbox in month view
scheduler.config.readonly_form = true;
// hide select bar in day and week views
scheduler.config.select = false;
scheduler.config.lightbox.sections = [
{
name: "description",
height: 20,
map_to: "text",
type: "textarea",
focus: true
}
];
scheduler.init(this.$refs.scheduler_here, new Date(), 'month');
scheduler.parse(this.$props.events, 'json');
},
}
</script>
<style lang="css" scoped>
#import "~dhtmlx-scheduler/codebase/dhtmlxscheduler.css";
</style>
getOgustData can't populate events in a way that Vue can observe. Since you're passing it as an argument, the array itself can be updated, but it's not a reactive array. Try
var newEvents;
await data.getOgustData(this, token, '/calendar/events', 307310564, newEvents);
this.events = newEvents;
Assigning to this.events is something Vue can notice.
Problem is solved. The issue didn't come from Vue but rather from the dhtmlx scheduler which wasn't parsing events when events was updated.
I ended up watching for any changes to events and thus, parsing it when it updates.
Thanks again for the help provided.
App.vue :
<template>
<div id="app">
<div class="container">
<scheduler v-bind:events="events"></scheduler>
</div>
</div>
</template>
<script>
import Scheduler from './components/Scheduler.vue';
import auth from './components/auth/index'
import data from './components/data/index'
import 'dhtmlx-scheduler'
export default {
name: 'app',
components: {
Scheduler
},
data() {
return {
events: []
}
},
created() {
this.getEvents();
},
watch: {
events: function(value) {
scheduler.parse(this.events, 'json');
}
},
methods: {
async getEvents() {
const token = await auth.getToken(this);
const apiToken = await auth.getApiToken(this, token);
this.events = await data.getApiData(this, apiToken, '/calendar/events', 307310564, this.events);
}
},
}
</script>