Mount component into DOM with JS in Vue.js - javascript

I'm making a GUI for a webgame with Vue.JS, now I'm using http-vue-loader to avoid using Webpack and similar (sorry but I really hate it).
Now, usually what I do for get my component into my DOM is this:
GUI.js
// Creating Vue.js istance for GUI
Pixua.GUI = new Vue({
el: '#GUI',
components: {
'Debug': httpVueLoader('./gui/windows/Debug.vue')
},
data: {
row: 0,
column: 0
}
});
Index.html
<div id="GUI">
<windows id="windows">
<debug v-bind:row="row" v-bind:column="column" ></debug>
</windows>
</div>
Debug.vue (it's my component)
<template>
<px-window title="Debug" :width="200" :is-open.sync="isOpen">
Casella cliccata:
<ul>
<li>Riga: {{row}}</li>
<li>Colonna: {{column}}</li>
</ul>
</px-window>
</template>
<script>
module.exports = {
props: ['row','column'],
data: function() {
return {
isOpen: true
}
}
}
</script>
<style>
</style>
Now my question is: is possible to get the component mounted/rendered into the DOM dinamically with JS (via GUI.js) and without specifying it into the index.html (so without <debug v-bind:row="row" v-bind:column="column" ></debug>)?

You can use Dynamic components
<component v-bind:is="myComponent"></component>
And then change the value of myComponent to the component you want to load.
data() {
return {
myComponent: 'debug'
}
}
Will render the debug component

Related

Mixin for destroyed Vue component is still listening for events

I have a parent component that conditionally renders one of two child components:
<template>
<div>
<!-- other code that changes conditional rendering -->
<folders v-if="isSearchingInFolders" :key="1234"></folders>
<snippets v-if="!isSearchingInFolders" :key="5678"></snippets>
</div>
</template>
Each of these components use the same mixin (searchMixin) locally like so:
<template>
<div>
<div>
<snippet
v-for="item in items"
:snippet="item"
:key="item.id">
</snippet>
<img v-if="busy" src="/icons/loader-grey.svg" width="50">
</div>
<button #click="getItems">Get More</button>
</div>
</template>
<script>
import searchMixin from './mixins/searchMixin';
import Snippet from './snippet';
export default {
components: { Snippet },
mixins: [searchMixin],
data() {
return {
resourceName: 'snippets'
}
},
}
</script>
Each of the components is functionally equivalent with some slightly different markup, so for the purposes of this example Folders can be substituted with Snippets and vice versa.
The mixin I am using looks like this (simplified):
import axios from 'axios'
import { EventBus } from '../event-bus';
export default {
data() {
return {
hasMoreItems: true,
busy: false,
items: []
}
},
created() {
EventBus.$on('search', this.getItems)
this.getItems();
},
destroyed() {
this.$store.commit('resetSearchParams')
},
computed: {
endpoint() {
return `/${this.resourceName}/search`
},
busyOrMaximum() {
return this.busy || !this.hasMoreItems;
}
},
methods: {
getItems(reset = false) {
<!-- get the items and add them to this.items -->
}
}
}
In the parent component when I toggle the rendering by changing the isSearchingInFolders variable the expected component is destroyed and removed from the DOM (I have checked this by logging from the destroyed() lifecycle hook. However the searchMixin that was included in that component does not appear to be destroyed and still appears to listen for events. This means that when the EventBus.$on('search', this.getItems) line is triggered after changing which component is actively rendered from the parent, this.getItems() is triggered twice. Once for folders and once for snippets!
I was expecting the mixins for components to be destroyed along with the components themselves. Have I misunderstood how component destruction works?
Yes, when you pass an event handler as you do EventBus keeps the reference to the function you passed into. That prevents the destruction of the component object. So you need clear the reference by calling EventBus.$off so that the component can be destructed. So your destroy event hook should look like this:
destroyed() {
this.$store.commit('resetSearchParams')
EventBus.$off('search', this.getItems)
},

How to call/initlialize a vue method from outside of the Vue APP

I have an app that is mounted within the page of a preexisting website. In order to initialize the app i have a button within my Vue app to toggle/start the actual Vue logic.
It is all very straight forward, a button appears on the page, click it, the app logic and methods all come to life.
<template>
<button #click.prevent="toggle">Click me</button>
</template>
<script>
export default {
computed: {
isBurgerActive() {
return this.$store.getters.getIsNavOpen;
}
},
methods: {
toggle() { <----- CALL THIS METHOD OUTSIDE OF VUE
this.$store.dispatch('toggleNav');
this.$store.dispatch('getProduct');
}
}
}
</script>
What i want to do is initialize this app with a button that is not within the Vue app. Essentially saying on click of button outside of the Vue app, initialize the toggle() method and start the whole process off.
Is that possible?
If you assign the component to a variable. You can call the methods on that variable. In fact, you can access other properties of vue from that variable (ex: refs, data).
Try the snipet below:
var app = new Vue({
el: "#app",
data() {
return {
isActive: false,
}
},
computed: {
isBurgerActive() {
return this.isActive ? 'active':'inactive'
}
},
methods: {
toggle() {
this.isActive = !this.isActive;
}
},
})
document.getElementById('btn').addEventListener("click", function() {
app.toggle();
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div>
<button id="btn">
Click Me
</button>
</div>
<div id="app">
<h2>Active: {{ isBurgerActive }}</h2>
</div>

Creating/Destroying the Vue Component based on text search

I have the following in App.vue
<template>
<div id="app">
<input type="text" v-model="term">
<hello-world text="Button 1" v-if="term === ''"></hello-world>
<hello-world v-else text="Button 2"></hello-world>
</div>
</template>
<script>
import HelloWorld from '#/components/HelloWorld'
export default {
name: 'app',
data() {
return {
term: ''
}
},
components: {
HelloWorld
}
}
</script>
And here's the HelloWorld.vue:
<template>
<div>
<button>{{ text }}</button>
</div>
</template>
<script>
export default {
props: {
text: String
},
created() {
console.log('Created')
},
destroyed() {
console.log('Destroyed')
}
}
</script>
So, when I type something the first component should be destroyed and the second component should be created. However, nothing like that happens. The component neither gets destroyed nor gets created.
It's as if the v-if didn't trigger the created() & destroyed() function. Please help me with this.
Vue uses virtual dom approach. So, it is comparing the virtual tree and it is not identifying changes on structure (oldNode.type === newNode.type). When it occurs, Vue updates the same component instead of destroying the old node and creating a new one.
Try to force Vue to detect virtual tree changes avoiding use siblings with the same tag name and controlled by v-if directive.
Reference:
https://medium.com/#deathmood/how-to-write-your-own-virtual-dom-ee74acc13060
Vue.component('hello-world', {
props: {
text: String
},
created() {
console.log('Created')
},
destroyed() {
console.log('Destroyed')
},
template: "<button>{{ text }}</button>"
});
var app = new Vue({
el: "#app",
data() {
return {
term: ''
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" v-model="term">
<span><hello-world v-if="!term" text="Button 1"></hello-world></span>
<span><hello-world v-if="term" text="Button 2"></hello-world></span>
</div>
I am not sure what you are trying to achieve, but testing your code logs created from both components
https://codesandbox.io/s/8l0j43zy89
Since you are actually showing conditionally the same component, I don't think it will get destroyed.

Vue.js - Using Child Components in a Single File Component

I have a Vue.js app. In this app, I have a single file component. In this component, I want to have another component that's specific to the component. I'm trying to do something like this:
parent.vue
<template>
<div>
<child-component></child-component><br />
<child-component></child-component>
</div>
</template>
<script>
export default {
data() {
return {
info: 'hello'
}
},
components: {
childComponent: {
template: '<div>child</div>',
data() { return {}; }
}
}
}
</script>
After building with webpack, I then run my app in the browser. That app then generates an error in the console window that says:
Unknown custom element: - did you register the component correctly?
I believe I've set this up properly. However, clearly I haven't. What am I doing wrong? I can see where I may not have registered "child-component". However, I guess I'm not sure how to do that within a single file component. What am I missing?
Thank you,
Some ideas that might be helpful:
- as thanksd pointed out, register "child-component" intead of childComponent:
components: {
"child-component": {
template: '<div>child</div>',
data() { return {}; }
}
}
make sure you register the component before create the vue instance (this may or may not apply to your case, I cannot tell from the source code you posted:
Vue.component(child-component', {
template: 'child',
data() { return {}; }
})
new Vue({ el: '#app' })

Equivalent of a React container and presentational component in Vue.js

I would like to separate the business logic and the template of a component.
In React I would use the Container/Presentation pattern.
const Container = (props) => <Presentational ...props/>
But what is the equivalent with vue.js?
Say I have this all in one component (did not test this one, it just for example) :
<template>
<div id="app">
<div v-for="user in users">
{{user.name}}
</div>
</div>
</template>
<script>
Vue.component({
el: '#app',
props: {
filter: "foo"
},
data: {
users: [],
},
ready: function () {
this.$http.get('/path/to/end-point?filter='+this.props.filter)
.then(function (response) {
this.users = response.data
})
}
})
</script>
How I could extract a container with just the fetch logic?
I really don't see a need for a container component. Abstract your fetch code out into a vuex action and bind your store state within the component using computed properties.
You can extend any Vue component, which will allow you to override any methods from the parent. So, you can create a base component and then extend that:
const Base = Vue.component('base-comp', {
template: "#base",
data() {
return {
name: 'foo'
}
}
});
const Child = Base.extend({
data() {
return {
name: 'bar'
}
}
});
Vue.component('child-comp', Child)
You can check out the JSFiddle here: https://jsfiddle.net/tdgxdhz9/
If you are using single file components, then it's simply a case of importing the base component and extending it, which keeps the original template in tact:
import Base from './BaseComponent.vue'
Base.extend({
// Javascript code here
})
You can use vuex-connect to create container components like in React. Here an example of project : https://github.com/pocka/vue-container-component-example
What you are looking for is Vue Mixins.
You can write a mixins file which contains your business logic and import it into your Vue components.
Link:- https://v2.vuejs.org/v2/guide/mixins.html

Categories

Resources