I have a Vue Component, that uses the bootstrap grid. In the child component I would like to get the current width of a certain div in the controller.
heres the child component:
<template>
<div id="bg-prev" ref="prev">
<h1>{{x}}</h1>
</div>
<template>
export default {
props: ['section'],
data() {
return {
x: 0,
y: 0,
}
},
mounted() {
this.getWindowWidth();
},
methods: {
getWindowWidth() {
this.x = this.$refs.prev.clientWidth;
}
}
};
<style>
#bg-prev {
width: 100%;
}
</style>
In this example the width will always be 0, even though the element has a clear width when I inspect it. What am I missing here, the mounted hook is the latest one in the vue lifecycle right? Any Help is appreciated ;)
Apart from a few typos the code is perfectly working. (missing closing template tag)
There is no additional hook needed. Only exception would be if you toggle the rendering of the Component in it's mounted method. Then you would use something like
const that = this
that.showDiv = true
that.$nextTick(function () {
that.getWindowWidth()
})
Are you sure it's parent is having a width during mounted? 100% of 0px is still 0px.
Related
when embedding this single scene phaser game into React page, the scene was duplicated into two. And every time updating the code, 2 more duplications are added on the page.
Game componenet as below:
import MainScene from './scenes/MainScene.js';
import Phaser from 'phaser';
import React, { Component } from 'react'
class Game extends Component {
componentDidMount(){
const config = {
width: 640,
height: 1024,
backgroundColor: '#333333',
type: Phaser.AUTO,
parent: 'phaser-game',
scene: [MainScene]
};
new Phaser.Game(config);
}
shouldComponentUpdate() {
return false;
}
render() {
return <div id="game" />
}
}
export default Game;
here can see two canvas elements are created
thanks in advance!
I assume the problem lies in the config object, where you are setting the parent to phaser-game. parentis the idof the DOM-element, where the canvas should be injected. If an element with that specific id can't be found, it is injected into the body-tag.
So the solution is, to change the parent property from phaser-game to game, as this seems to be the id of the element, where you want the game to be displayed.
Just to show the connection, between the parent property and the element it refers to.
Here some code:
// ...
componentDidMount(){
const config = {
// ...
// "id" of the parent DOM Element
parent: 'game',
// ...
};
new Phaser.Game(config);
}
// ...
render() {
// parent DOM Element
return <div id="game" />
}
Update:
the problem could be that, componentDidMount can run multiple times as mentioned in this article, since I don't know your code and I'm no ReactJs expert, you could try to look into this article, how to prevent multiple call of the componentDidMount function.
This question already has an answer here:
Vue basics -- use component in other component
(1 answer)
Closed 2 years ago.
I am beginner in Vue. I created a custom component and tried to bind everything exactly as shown in the starter Vue cli template. Here is the code.
Circle.vue
<template>
<div :style="custom">
</div>
</template>
<script>
export default {
name:'Circle',
props:{
size:String,
color:String
},
computed:{
custom(){
return {
background:this.color,
height:this.size,
width:this.size
}
}
}
}
</script>
inside my View.vue file
<script>
// :class="['']"
import Circle from '#/components/Circle.vue'
export default {
name: "Landing",
components:{
Circle
}
};
</script>
I tried to use it so
<Circle size="100px" color="#222222"/>
I tried printing the props as it is but it also doesnt work
<template>
<div :style="custom">
{{size}} {{color}}
</div>
</template>
Nothing is being shown on the screen after I do this. I took help from here
Thanks for your time!
As mentioned in the docs:
Component names should always be multi-word, except for root App components, and built-in components provided by Vue, such as <transition> or <component>.
This prevents conflicts with existing and future HTML elements, since all HTML elements are a single word.
You have two options when defining component names:
With kebab-case
Vue.component('my-circle', { /* ... */ })
When defining a component with kebab-case, you must also use kebab-case when referencing its custom element, such as in <my-circle>.
With PascalCase
Vue.component('MyCircle', { /* ... */ })
When defining a component with PascalCase, you can use either case when referencing its custom element. That means both <my-circle> and <MyCircle> are acceptable.
Demo:
Vue.component('my-circle', {
props: {
size: String,
color: String
},
template: '<div :style="custom"></div>',
computed: {
custom() {
return {
background: this.color,
height: this.size,
width: this.size
}
}
}
})
new Vue({
el: "#myApp"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
<my-circle size="100px" color="#222222" />
</div>
I want to assign some attributes and classes to the children VNode through data object. That just works. But during my Vue.js investigation, I have not seen such pattern in use, that's why I don't think it's good idea to modify children VNode's.
But that approach sometimes comes in handy – for example I want to assign to all the buttons in default slot the aria-label attribute.
See example below, using default stateful components:
Vue.component('child', {
template: '<div>My role is {{ $attrs.role }}</div>',
})
Vue.component('parent', {
render(h) {
const {
default: defaultSlot
} = this.$slots
if (defaultSlot) {
defaultSlot.forEach((child, index) => {
if (!child.data) child.data = {}
if (!child.data.attrs) child.data.attrs = {}
const {
data
} = child
data.attrs.role = 'button'
data.class = 'bar'
data.style = `color: #` + index + index + index
})
}
return h(
'div', {
class: 'parent',
},
defaultSlot,
)
},
})
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<parent>
<child></child>
<child></child>
<child></child>
<child></child>
<child></child>
</parent>
</div>
And here is examples using stateless functional components:
Vue.component('child', {
functional: true,
render(h, {
children
}) {
return h('div', {
class: 'bar'
}, children)
},
})
Vue.component('parent', {
functional: true,
render(h, {
scopedSlots
}) {
const defaultScopedSlot = scopedSlots.default({
foo: 'bar'
})
if (defaultScopedSlot) {
defaultScopedSlot.forEach((child, index) => {
child.data = {
style: `color: #` + index + index + index
}
child.data.attrs = {
role: 'whatever'
}
})
}
return h(
'div', {
class: 'parent',
},
defaultScopedSlot,
)
},
})
new Vue({
el: '#app',
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<parent>
<template v-slot:default="{ foo }">
<child>{{ foo }}</child>
<child>{{ foo }}</child>
<child>{{ foo }}</child>
</template>
</parent>
</div>
I am waiting for the following answers:
Yes, you can use it, there are no potential problems with this approach.
Yes, but these problem(s) can happen.
No, there are a lot of problem(s).
UPDATE:
That another good approach I have found it's to wrap child VNode into the another created VNode with appropriate data object, like this:
const wrappedChildren = children.map(child => {
return h("div", { class: "foo" }, [child]);
});
Using this approach I have no fear modifying children VNode's.
Thank you in advance.
There are potential problems with doing this. Used very sparingly it can be a useful technique and personally I would be happy to use it if no simple alternative were available. However, you're in undocumented territory and if something goes wrong you'll likely have to debug by stepping through Vue internals. It is not for the faint-hearted.
First, some of examples of something similar being used by others.
Patching key:
https://medium.com/dailyjs/patching-the-vue-js-virtual-dom-the-need-the-explanation-and-the-solution-ba18e4ae385b
An example where Vuetify patches a VNode from a mixin:
https://github.com/vuetifyjs/vuetify/blob/5329514763e7fab11994c4303aa601346e17104c/packages/vuetify/src/components/VImg/VImg.ts#L219
An example where Vuetify patches a VNode from a scoped slot: https://github.com/vuetifyjs/vuetify/blob/7f7391d76dc44f7f7d64f30ad7e0e429c85597c8/packages/vuetify/src/components/VItemGroup/VItem.ts#L58
I think only the third example is really comparable to the patching in this question. A key feature there is that it uses a scoped slot rather than a normal slot, so the VNodes are created within the same render function.
It gets more complicated with normal slots. The problem is that the VNodes for the slot are created in the parent's render function. If the child's render function runs multiple times it'll just keep getting passed the same VNodes for the slot. Modifying those VNodes won't necessarily do what you'd expect as the diffing algorithm just sees the same VNodes and doesn't perform any DOM updates.
Here's an example to illustrate:
const MyRenderComponent = {
data () {
return {
blueChildren: true
}
},
render (h) {
// Add a button before the slot children
const children = [h('button', {
on: {
click: () => {
this.blueChildren = !this.blueChildren
}
}
}, 'Blue children: ' + this.blueChildren)]
const slotContent = this.$slots.default
for (const child of slotContent) {
if (child.data && child.data.class) {
// Add/remove the CSS class 'blue'
child.data.class.blue = this.blueChildren
// Log it out to confirm this really is happening
console.log(child.data.class)
}
children.push(child)
}
return h('div', null, children)
}
}
new Vue({
el: '#app',
components: {
MyRenderComponent
},
data () {
return {
count: 0
}
}
})
.red {
border: 1px solid red;
margin: 10px;
padding: 5px;
}
.blue {
background: #009;
color: white;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<my-render-component>
<div :class="{red: true}">This is a slot content</div>
</my-render-component>
<button #click="count++">
Update outer: {{ count }}
</button>
</div>
There are two buttons. The first button toggles a data property called blueChildren. It's used to decide whether or not to add a CSS class to the children. Changing the value of blueChildren will successfully trigger a re-render of the child component and the VNode does get updated, but the DOM is unchanged.
The other button forces the outer component to re-render. That regenerates the VNodes in the slot. These then get passed to the child and the DOM will get updated.
Vue is making some assumptions about what can and can't cause a VNode to change and optimising accordingly. In Vue 3 this is only going to get worse (by which I mean better) because there are a lot more of these optimisations coming along. There's a very interesting presentation Evan You gave about Vue 3 that covers the kinds of optimisations that are coming and they all fall into this category of Vue assuming that certain things can't change.
There are ways to fix this example. When the component is performing an update the VNode will contain a reference to the DOM node, so it can be updated directly. It's not great, but it can be done.
My own feeling is that you're only really safe if the patching you're doing is fixed, such that updates aren't a problem. Adding some attributes or CSS classes should work, so long as you don't want to change them later.
There is another class of problems to overcome. Tweaking VNodes can be really fiddly. The examples in the question allude to it. What if data is missing? What if attrs is missing?
In the scoped slots example in the question the child component has class="bar" on its <div>. That gets blown away in the parent. Perhaps that's intentional, perhaps not, but trying to merge together all the different objects is quite tricky. For example, class could be a string, object or array. The Vuetify example uses _b, which is an alias for Vue's internal bindObjectProps, to avoid having to cover all the different cases itself.
Along with the different formats are the different node types. Nodes don't necessarily represent components or elements. There are also text nodes and comments, where comment nodes are a consequence of v-if rather than actual comments in the template.
Handling all the different edge cases correctly is pretty difficult. Then again, it may be that none of these edge cases cause any real problems for the use cases you actually have in mind.
As a final note, all of the above only applies to modifying a VNode. Wrapping VNodes from a slot or inserting other children between them in a render function is perfectly normal.
I have navbar blade, component with text and another components with page.
It works like I have component with text in navbar, and another component after navbar. That's three another components. How to change text from for example index.vue in text.vue?
That's what I have:
Text.vue:
<template>
<p class="title">{{msg}}</p>
</template>
<script>
export default {
props: [
'msg',
],
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
Component in navbar.blade.php:
<navbar-title></navbar-title>
And I try to change it in index.vue, that should work when we are on this page:
data() {
return {
msg: 'text',
}
But it doesn't work. How to do it correctly?
EDIT:
Vue.component('title', require('./components/Title.vue'));
To pass the message variable from your index.vue through navbar.vue to title.vue each needs to pass the property to the child and each child must pass the property on again all throughout the tree.
Something like this should work for your case: <title :msg="msg"></title>
I'm new to vue js and missing passing components prop.
Trying to make the vue-slider-component example to be vertical.
<vue-slider direction=vertical ></vue-slider>
and
<vue-slider :direction=vertical ></vue-slider>
failed, how should I pass props to component in the example?
Edit:
Tried what was suggested in comments:
<vue-slider direction={vertical} ></vue-slider>
and
<vue-slider :direction={vertical} ></vue-slider>
You should pass the value as string like this:
<vue-slider :direction="'vertical'"></vue-slider>
See the sample here
new Vue( {
el: '#app',
data () {
return {
}
},
methods: {
},
mounted () {
},
components: {
'vueSlider': window[ 'vue-slider-component' ],
}
})
#app {
margin: 50px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<script src="https://nightcatsama.github.io/vue-slider-component/dist/index.js"></script>
<div id="app">
<vue-slider :direction="'vertical'" :height="100" :width="6"></vue-slider>
</div>
The answer you're after is the magical
#app {
transform: rotate(90deg);
}
i'm not sure if this can be done via props... i'm honestly not all that familiar with vue-js so sorry to jump the gun on the answer. =)
to be more concise. Add a custom class to your slider and apply the rotate accordingly (so that you're not rotating you entire app =)