Vue.js - multiple event with dynamic components - javascript

I have a simple root App which includes two different components Room and Machine. And each of these components include one component Datatable which is totally same for both of them. To switch between Room and Machine I'm using dynamic components mechanism, nothing special. When component is changing then I just emit event and component Datatable should log it in console with current module name. The problem is that each time I change component, event is sent multi times. If I understand right, after changing component the old one should be destroyed and the new one created so why this is happening? I'm using Vue.js v.2.4.1
Here is screenshot from console when I switch between components:
Here are components:
App.vue:
<template>
<div id="app">
<select style="padding: 10px" v-model="currentModule" #change="changeComponent">
<option value="Room">Room</option>
<option value="Machine">Machine</option>
</select>
<component :is="currentModule"></component>
</div>
</template>
<script>
import Room from './Room.vue';
import Machine from './Machine.vue';
export default {
name: 'app',
components: {
Room,
Machine
},
data () {
return {
currentModule: 'Room'
}
},
methods: {
changeComponent: function() {
Event.$emit('moduleHasChanged', this.currentModule)
}
},
}
</script>
Machine.vue:
<template>
<div>
Machine template
<datatable></datatable>
</div>
</template>
<script>
import Datatable from './Datatable.vue';
export default {
components: {
Datatable
}
}
</script>
Room.vue:
<template>
<div>
Room template
<datatable></datatable>
</div>
</template>
<script>
import Datatable from './Datatable.vue';
export default {
components: {
Datatable
}
}
</script>
Datatable.vue
<template>
<div>
Datatable
</div>
</template>
<script>
export default {
created() {
Event.$on('moduleHasChanged', (currentModule) => {
console.log(currentModule);
})
}
}
</script>

This is happening because you are continually adding event listeners (when the Datatable component is created) and never removing them. Vue typically handles this for you, but since you are using an event bus you need to do it yourself.
Simply add a beforeDestroy handler and remove the event handler.
console.clear()
const Event = new Vue()
const Datatable = {
template: `
<div>
Datatable
</div>
`,
methods: {
moduleChanged(currentModule) {
console.log(currentModule);
}
},
created() {
Event.$on('moduleHasChanged', this.moduleChanged)
},
beforeDestroy() {
Event.$off('moduleHasChanged', this.moduleChanged)
}
}
const Room = {
template: `
<div>
Room template
<datatable></datatable>
</div>
`,
components: {
Datatable
}
}
const Machine = {
template: `
<div>
Machine template
<datatable></datatable>
</div>
`,
components: {
Datatable
}
}
const App = {
name: 'app',
template: `
<div id="app">
<select style="padding: 10px" v-model="currentModule" #change="changeComponent">
<option value="Room">Room</option>
<option value="Machine">Machine</option>
</select>
<component :is="currentModule"></component>
</div>
`,
components: {
Room,
Machine
},
data() {
return {
currentModule: 'Room'
}
},
methods: {
changeComponent: function() {
Event.$emit('moduleHasChanged', this.currentModule)
}
},
}
new Vue({
el: "#app",
render: h => h(App)
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
</div>

Related

Use keyup enter on custom input field - Vue JS

I have to use keypu.enter in a custom input component on Vue. I want that the component <input /> can execute a function hosted in parent component after a enter key event during typing
This is the component code
<input v-on:keyup.enter="$emit('keyup')"/>
And there are the main page
<template>
<se-input #keyup="function()"/>
</template>
<script>
import inputField from '../components/inputfield.vue'
export default {
name: 'inputField',
components: {
'custom-input': inputField
},
},
methods: {
function () {
// Function
}
}
}
</script>
Try to pass value to your custom event, and in parent component listen to that event :
Vue.component('seInput', {
template: `
<div class="">
<input v-on:keyup.enter="$emit('keyup', $event.target.value)"/>
</div>
`
})
new Vue({
el: '#demo',
data() {
return {
inputValue: null
}
},
methods: {
handleInput(val) {
this.inputValue = val
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<h3>{{ inputValue }}</h3>
<se-input #keyup="handleInput"/>
</div>

How to pass data from Vue.js to Blade view?

I just can't find a way to pass my data from vue.js component to blade view in my Laravel project. I tried to use hidden inputfield but data binding returns [object Object].
Any help would be appreciated.
Create a variable in the root component data object and change it from the child component so assuming resources/js/components/ExampleComponent.vue like this
<template>
<div class="container">
<input v-model="field" type="text">
</div>
</template>
<script>
export default {
data() {
return {
field: ''
}
},
watch: {
field: function (val) {
this.$root.bladeValue = val;
}
}
}
</script>
and a resources/js/app.js like so
window.Vue = require('vue');
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
const app = new Vue({
el: '#app',
data() {
return {
bladeValue: ''
}
}
});
and a blade view like so resources/views/welcome.blade.php
<div id="app">
<example-component></example-component>
<h1>#{{ bladeValue }}</h1>
</div>
<script src="/js/app.js"></script>
Then bladeValue will be binded to field in ExampleComponent

VueJS 2 show some HTML if a Function prop was passed

Question
In VueJS 2 how do you show some HTML if a Function prop was passed to the component.
Example
<template>
<div v-if="backBtn" #click="$emit('backBtn')">Back Button</div>
</template>
<script>
export default {
props: {
backBtn: Function
}
}
</script>
I can do this by passing a separate prop to key the v-if off of but I'm trying to do this will the one prop.
I created a Fiddle for this issue here
that should work,
you can add more definition with !== undefined
<template>
<div v-if="backBtn !== undefined" #click="$emit('backBtn')">Back Button</div>
</template>
<script>
export default {
props: {
backBtn: {
type: Function,
},
}
}
</script>
but as mentioned, that should work already, so you error may be somewhere else.
after seeing your code, I see what the issue is. it's a case issue
use :back-btn instead of :backBtn
this happens only if you're using vue runtime only (without the compilation)
read more here:
https://v2.vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only
you can solve it also by passing the function only
https://jsfiddle.net/rz6hyd7b/7/
Vue.component('my-btn', {
props: {
backbtn: {
type: Function
}
},
template: `
<div>
<div v-if="backbtn" #click="backbtn">Back Button</div>
</div>
`
})
var vm = new Vue({
el: '#app',
components: 'my-btn',
methods: {
btnClicked: function(){
console.log('adsf')
}
},
template: `
<div>
Show Btn => <my-btn :backbtn="btnClicked"></my-btn>
</br>
Hidden Btn => <my-btn></my-btn>
</div>
`
});

Vue.js use correct component

I've got a main-component in that main-component I've a sub-component with vue.js 2.0.
The problem is that the sub-component uses the methods in the main-component.
I've made an example:
Vue.component('main-component', {
template: '<p>This is the main component. <sub-component><button #click="test()">If this button is presses: "sub-component" must show up. </button></sub-component></p>',
methods: {
test() {
alert('main-component');
}
}
})
Vue.component('sub-component', {
template: '<p>This is sub-component <slot></slot> </p>',
methods: {
test() {
alert('sub-component');
}
}
})
var app = new Vue({
el: '#app'
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<main-component></main-component>
</div>
How do I make sure the sub-component uses it's own methods, and in this case give an alert of: 'sub-component' instead of 'main-component' when the button is being pressed?
Use a scoped slot.
Vue.component('main-component', {
template: `
<p>This is the main component.
<sub-component>
<template scope="{test}">
<button #click="test()">If this button is presses: "sub-component" must show up. </button>
</template>
</sub-component>
</p>`,
methods: {
test() {
alert('main-component');
}
}
})
Vue.component('sub-component', {
template: '<p>This is sub-component <slot :test="test"></slot> </p>',
methods: {
test() {
alert('sub-component');
}
}
})
var app = new Vue({
el: '#app'
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="app">
<main-component></main-component>
</div>
Perhaps you can try something like this:
<sub-component ref="sub"><button #click="$refs.sub.test()">...</button></sub-component>

Mount parent component in child

I have two files named Recursive.vue and Value.vue.
In the first instance Recursive is the parent. Mounting Recursive in Recursive goes great, same for mounting Value in Recursive and after that Value in Value.
But when I've mounted Value in Recursive and trying to mount Recursive in Value after that I get the following error:
[Vue warn]: Failed to mount component: template or render function not defined.
(found in component <recursive>)
How can I make my problem work?
This is what my files are looking like:
Recursive
<template>
<div class="recursive">
<h1 #click="toggle">{{comps}}</h1>
<div v-if="isEven">
Hello
<value :comps="comps"></value>
</div>
</div>
</template>
<script>
import Value from './Value.vue'
export default {
name: 'recursive',
components: {
Value
},
props: {
comps: Number
},
computed: {
isEven () {
return this.comps % 2 == 0;
}
},
methods: {
toggle () {
this.comps++;
}
}
}
</script>
Value
<template>
<div class="value">
<h1 #click="toggle">{{comps}}</h1>
<div v-if="isEven">
<recursive :comps="comps"></recursive>
</div>
</div>
</template>
<script>
import Recursive from './Recursive.vue'
export default {
name: 'value',
components: {
Recursive
},
props: {
comps: Number
},
computed: {
isEven () {
return this.comps % 2 == 0;
}
},
methods: {
toggle () {
this.comps++;
}
}
}
</script>
Mounter
<template>
<div class="mounter">
<h1>HI</h1>
<recursive :comps="comps"></recursive>
</div>
</template>
<script>
import Recursive from './Recursive'
export default {
name: 'mounter',
components: {
Recursive
},
data () {
return {
comps: 0
}
}
}
</script>
I had a similar problem before. The only way out was declaring the component as "global", because importing it in the component which actually required it never worked.
new Vue({
...
})
Vue.component('recursive', require('./Recursive'))
Then you can just use without importing:
// Mounted
<template>
<div class="mounter">
<h1>HI</h1>
<recursive :comps="comps"></recursive>
</div>
</template>
<script>
export default {
name: 'mounter',
data () {
return {
comps: 0
}
}
}
</script>

Categories