Is that the right way to set an id in Vue.js component? - vuejs2

I am trying to integrate Phaser 3 with Vue.js 2.
My goal is to create a Vue.js component associated with a game canvas.
My initial solution was:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
mounted () {
this.id = 'game' + this._uid
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
This code attach the game canvas to my template. But to my surprise it only worked 'sometimes'.
I figured out, after hours of debugging, that my div element in the DOM wasn't updated with the id when I was instantiating my new Game.
So I came up with the solution of instantiating the id in the beforeMount () method as follow:
<template>
<div :id="id">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
id: null,
game: null
}
},
beforeMount () {
this.id = 'game' + this._uid
},
mounted () {
var config = {
parent: this.id,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>
It is working, but I would like to know if there is a more simple and elegant solution ?

One better solution for integrating Phaser.Game into the application is directly passing the config the HTML element, a configuration supported by Phaser.Game.
To get a reference to a HTML element in vue, you can use refs, these are basically id's, but local to the component itself, so there is not risk in creating conflicts.
<template>
<div ref="myDiv">
</div>
</template>
<script>
import Phaser from 'phaser'
export default {
data () {
return {
game: null
}
},
mounted () {
var config = {
parent: this.$refs.myDiv,
type: Phaser.CANVAS
}
this.game = new Phaser.Game(config)
....
}
}
</script>

Vue3 sample:
<script setup>
import { ref,onMounted } from 'vue';
import Phaser from 'phaser'
const myDiv = ref(null)
let canvasWidth = 750;
let canvasHeight = 1450;
onMounted(() => {
const config = {
type: Phaser.AUTO,
parent: popWrap.value,
width: canvasWidth,
height: canvasHeight,
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
})
</script>
<template>
<div ref="myDiv">
</div>
</template>

Related

Is there a way to share reactive data between random components in Vue 3 Composition API?

Having some reactive const in "Component A," which may update after some user action, how could this data be imported into another component?
For example:
const MyComponent = {
import { computed, ref } from "vue";
setup() {
name: "Component A",
setup() {
const foo = ref(null);
const updateFoo = computed(() => foo.value = "bar");
return { foo }
}
}
}
Could the updated value of 'foo' be used in another Component without using provide/inject?
I am pretty new in the Vue ecosystem; kind apologies if this is something obvious that I am missing here.
One of the best things about composition API is that we can create reusable logic and use that all across the App. You create a composable functions in which you can create the logic and then import that into the components where you want to use it. Not only does this make your component much cleaner but also your APP much more maintainable. Below is a simple example of counter to show how they can be used. You can find working demo here:
Create a composable function for counter:
import { ref, computed } from "vue";
const counter = ref(0);
export const getCounter = () => {
const incrementCounter = () => counter.value++;
const decrementCounter = () => counter.value--;
const counterPositiveOrNegitive = computed(() =>
counter.value >= 0 ? " Positive" : "Negitive"
);
return {
counter,
incrementCounter,
decrementCounter,
counterPositiveOrNegitive
};
};
Then you can import this function into your components and get the function or you want to use. Component to increment counter.
<template>
<div class="hello">
<h1>Component To Increment Counter</h1>
<button #click="incrementCounter">Increment</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "IncrementCounter",
setup() {
const { incrementCounter } = getCounter();
return { incrementCounter };
},
};
</script>
Component to decrement counter:
<template>
<div class="hello">
<h1>Component To Decrement Counter</h1>
<button #click="decrementCounter">Decrement</button>
</div>
</template>
<script>
import { getCounter } from "../composables/counterExample";
export default {
name: "DecrementCounter",
setup() {
const { decrementCounter } = getCounter();
return { decrementCounter };
},
};
</script>
Then in the main component, you can show the counter value.
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div class="counters">
<IncrementCounter />
<DecrementCounter />
</div>
<h3>Main Component </h3>
<p>Counter: {{ counter }}</p>
<p>{{ counterPositiveOrNegitive }}</p>
</template>
<script>
import IncrementCounter from "./components/IncrementCounter.vue";
import DecrementCounter from "./components/DecrementCounter.vue";
import { getCounter } from "./composables/counterExample";
export default {
name: "App",
components: {
IncrementCounter: IncrementCounter,
DecrementCounter: DecrementCounter,
},
setup() {
const { counter, counterPositiveOrNegitive } = getCounter();
return { counter, counterPositiveOrNegitive };
},
};
Hope this was somewhat helpful. You can find a working example here:
https://codesandbox.io/s/vue3-composition-api-blfpj

How to dynamically switch a component based on the existence of window in Nuxt.js?

I have a dynamic component that looks different at different screen resolutions.
<template>
<div>
<headerComponent></headerComponent>
<div v-if="!large" class="placeholder"></div>
<component
v-else
:is="tariffBlock"
>
</component>
</div>
</template>
<script>
import smallComponent from '#/components/small-component'
import largeComponent from '#/components/large-component'
import headerComponent from '#/components/header-component'
const components = {
smallComponent,
largeComponent
}
export default {
components: {
headerComponent
},
data () {
return {
large: false
}
},
computed: {
getComponent () {
if (!this.large) return components.smallComponent
return components.largeComponent
}
},
created () {
if (process.browser) {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
}
}
</script>
By default, a smallComponent is shown, and then a largeComponent. To avoid "jumping" I decided to show the placeholder while large === false.
To avoid the error window in not defined I use the check for process.browser.
PROBLEM: placeholder is only shown in dev mode, but when I start generate the placeholder is not displayed.
The following solutions DIDN'T help:
1.
created () {
this.$nextTick(() => {
if (process.browser) {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
})
}
created () {
this.$nextTick(() => {
this.large = window.matchMedia('(min-width: 1200px)').matches
})
}
mounted () {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
and with the addition process.browser and nextTick()
Creating a mixin with ssr: false, mode: client
Thanks in advance!
This is how you toggle between components in Nuxt.js
<template>
<div>
<div #click="toggleComponents">toggle components</div>
<hr />
<first-component></first-component>
<second-component></second-component>
<hr />
<component :is="firstOrSecond"></component>
</div>
</template>
<script>
export default {
data() {
return {
firstOrSecond: 'first-component',
}
},
methods: {
toggleComponents() {
if (this.firstOrSecond === 'first-component') {
this.firstOrSecond = 'second-component'
} else {
this.firstOrSecond = 'first-component'
}
},
},
}
</script>
You don't need to import them, it's done automatically if you have the right configuration, as explained here: https://nuxtjs.org/blog/improve-your-developer-experience-with-nuxt-components
In this snippet of code, first-component and second-component are shown initially (between the two hr) just to be sure that you have them properly loaded already. You can of course remove them afterwards.
Not recommended
This is what you're looking for. Again, this is probably not how you should handle some visual changes. Prefer CSS for this use-case.
<template>
<div>
<component :is="firstOrSecond"></component>
</div>
</template>
<script>
export default {
data() {
return {
firstOrSecond: 'first-component',
}
},
mounted() {
window.addEventListener('resize', this.toggleComponentDependingOfWindowWidth)
},
beforeDestroy() {
// important, otherwise you'll have the eventListener all over your SPA
window.removeEventListener('resize', this.toggleComponentDependingOfWindowWidth)
},
methods: {
toggleComponentDependingOfWindowWidth() {
console.log('current size of the window', window.innerWidth)
if (window.innerWidth > 1200) {
this.firstOrSecond = 'second-component'
} else {
this.firstOrSecond = 'first-component'
}
},
},
}
</script>
PS: if you really wish to use this solution, at least use a throttle because the window event will trigger a lot and it can cause your UI to be super sluggish pretty quickly.

How to fix Vue 3 template compilation error : v-model value must be a valid JavaScript member expression?

I am working on a vue project and the vue version is 3.0
And recently I can see these many warnings for some reason.
Template compilation error: v-model value must be a valid JavaScript member expression
I guess it is because I am using long v-model variable name like this.
<textarea v-model="firstVariable.subVariable.subVariableKey" readonly></textarea>
Please let me know if any idea.
Thanks in advance
This is the component and template code.
var myTemplate = Vue.defineComponent({
template: '#myTemplate',
data() {
return {
firstVariable: {}
}
},
mounted() {
loadData();
},
methods:{
loadData() {
axios.get(MY_ROUTES).then(res => {
// let's suppose res.data is going to be {subVariable: {subVariableKey: "val"}}
this.firstVariable = res.data;
})
}
}
});
// template.html
<script type="text/template" id="myTemplate">
<div class="container">
<textarea v-model="firstVariable.subVariable?.subVariableKey"></textarea>
</div>
</script>
In order that your property go reactive you've to define its full schema :
data() {
return {
firstVariable: {
subVariable: {
subVariableKey: ''
}
}
}
},
and use it directly without optional chaining
v-model="firstVariable.subVariable.subVariableKey"
because v-model="firstVariable.subVariable?.subVariableKey" malformed expression like v-model="a+b" like this test
Example
var comp1 = Vue.defineComponent({
name: 'comp1',
template: '#myTemplate',
data() {
return {
firstVariable: {
subVariable: {
subVariableKey: ''
}
}
}
},
mounted() {
this.loadData();
},
methods: {
loadData() {
}
}
});
const {
createApp
} = Vue;
const App = {
components: {
comp1
},
data() {
return {
}
},
mounted() {
}
}
const app = createApp(App)
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" >
vue 3 app
<comp1 />
</div>
<script type="text/template" id="myTemplate">
<div class="container">
<textarea v-model="firstVariable.subVariable.subVariableKey"></textarea>
<div>
{{firstVariable.subVariable.subVariableKey}}
</div>
</div>
</script>
You are adding a new property to an object which is not reactive.
Vue cannot detect property addition or deletion. Since Vue performs
the getter/setter conversion process during instance initialization, a
property must be present in the data object in order for Vue to
convert it and make it reactive. For example:
Source
Instead of
this.firstVariable = res.data;
Use
this.$set(this.firstVariable, 'subVariable', res.data.subVariable);

vuejs treeselect - delay loading does not work via vuex action

Using Vue TreeSelect Plugin to load a nested list of nodes from firebase backend. It's doc page says,
It's also possible to have root level options to be delayed loaded. If no options have been initially registered (options: null), vue-treeselect will attempt to load root options by calling loadOptions({ action, callback, instanceId }).
loadOptions (in my App.vue) dispatch vuex action_FolderNodesList, fetches (from firebase) formats (as required by vue-treeselect), and mutates the state folder_NodesList, then tries to update options this.options = this.get_FolderNodesList but this does not seems to work.
Here is the loadOptions method (in app.vue)
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
Vue errors out with Invalid prop: type check failed for prop "options". Expected Array, got String with value ""
I am not sure what am I doing wrong, why that does not work. A working Codesandbox demo
Source
App.vue
<template>
<div class="section">
<div class="columns">
<div class="column is-7">
<div class="field">
<Treeselect
:multiple="true"
:options="options"
:load-options="loadOptions"
:auto-load-root-options="false"
placeholder="Select your favourite(s)..."
v-model="value" />
<pre>{{ get_FolderNodesList }}</pre>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import Treeselect from "#riophae/vue-treeselect";
import "#riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
data() {
return {
value: null,
options: null,
called: false
};
},
components: {
Treeselect
},
computed: mapGetters(["get_FolderNodesList"]),
methods: {
loadOptions() {
let getFolderListPromise = this.$store.dispatch("action_FolderNodesList");
getFolderListPromise.then(_ => {
this.options = this.get_FolderNodesList;
});
}
}
};
</script>
Store.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
folder_NodesList: ""
},
getters: {
get_FolderNodesList(state) {
return state.folder_NodesList;
}
},
mutations: {
mutate_FolderNodesList(state, payload) {
state.folder_NodesList = payload;
}
},
actions: {
action_FolderNodesList({ commit }) {
fmRef.once("value", snap => {
var testObj = snap.val();
var result = Object.keys(testObj).reduce((acc, cur) => {
acc.push({
id: cur,
label: cur,
children: recurseList(testObj[cur])
});
return acc;
}, []);
commit("mutate_FolderNodesList", result);
});
}
}
});
Any help is appreciated.
Thanks
It seems you are calling this.options which would update the entire element while only the current expanding option should be updated.
It seems loadOptions() is called with some arguments that you can use to update only the current childnode. The first argument seems to contain all the required assets so I wrote my loadTreeOptions function like this:
loadTreeOptions(node) {
// On initial load, I set the 'children' to NULL for nodes to contain children
// but inserted an 'action' string with an URL to retrieve the children
axios.get(node.parentNode.action).then(response => {
// Update current node's children
node.parentNode.children = response.data.children;
// notify tree to update structure
node.callback();
}).catch(
errors => this.onFail(errors.response.data)
);
},
Then I set :load-options="loadTreeOptions" on the <vue-treeselect> element on the page. Maybe you were only missing the callback() call which updates the structure. My installation seems simpler than yours but it works properly now.

How to address the data of a component from within that component?

In a standalone Vue.js script I can mix functions and Vue data:
var vm = new Vue ({
(...)
data: {
number: 0
}
(...)
})
function return100 () {
return 100
}
vm.number = return100()
I therefore have a Vue instance (vm) which data is directly addressable via vm.<a data variable>)
How does such an addressing works in a component, since no instance of Vue is explicitly instantiated?
// the component file
<template>
(...)
</template>
<script>
function return100 () {
return 100
}
export default {
data: function () {
return {
number: 0
}
}
}
// here I would like to set number in data to what return100()
// will return
??? = return100()
</script>
You can achieve the target by using code like this.
<template>
<div>{{ name }}</div>
</template>
<script>
const vm = {
data() {
return {
name: 'hello'
};
}
};
// here you can modify the vm object
(function() {
vm.data = function() {
return {
name: 'world'
};
}
})();
export { vm as default };
</script>
But I really don't suggest you to modify data in this way and I think it could be considered as an anti-pattern in Vuejs.
In almost all the use cases I met, things could be done by using Vue's lifecycle.
For example, I prefer to write code with the style showed below.
<template>
<div>{{ name }}</div>
</template>
<script>
export default {
data() {
return {
name: 'hello'
};
},
mounted() {
// name will be changed when this instance mounted into HTML element
const vm = this;
(function() {
vm.name = 'world';
})();
}
};
</script>