Vue - How to render $slots.default in a div to calculate its height? - vue.js

I'm trying to simplify my problem:
Let us say I have a modal component
//example.php
<modal>
<div>A big div</div>
</modal>
Before the modal is shown I need to calculate the height for the proper animation. Inside the modal Vue it looks like this:
//Modal.vue
...
<transition
:name="transition"
#before-enter="beforeTransitionEnter"
#after-leave="afterTransitionLeave"
>
<div
v-if="visibility.modal"
ref="modal"
class="v--modal v--modal-box"
:style="modalStyle"
>
<slot/>
</div>
</transition>
I know that with this.$slots.default I get the node of the slot.
But I'm not sure how I can create a div, add the node to the div so that I can then calculate the height of it?
Edit:
Is it possible to call your own render function so I can use it like in the docs?
render: function (createElement) {
// `<div><slot></slot></div>`
return createElement('div', this.$slots.default)
}
Like
guessSlotsHeight(){
let modalDiv = document.createElement('div')
modalDiv.className = 'v--modal v--modal-box'
//something like
let slotDiv = this.render('div', this.$slots.default)
modalDiv.appendChild(slotDiv);
},

Your logic is too complex. It's easiest than you think.
Look, animation is the thing which happens to DOM. Therefore, DOM is available when animation starts. So, you can access any DOM element using Vue ref (recommended) or native javascript syntax in mounted hook. And there you can take the element height or any other property you want and store in in your data (for example).
Possible solution example #1
Vue transition before-enter event function has the target element as an argument by default. So, you can get the modal height in your beforeTransitionEnter function:
Modal.vue
data() {
return {
modalHeight: 0,
visibility: {
modal: false
}
}
},
methods: {
beforeTransitionEnter(element) {
this.modalHeight = element.offsetHeight;
}
}
Possible solution example #2
For this example there is one important detail. I see in your code example that you display the next div conditionally:
`<div v-if="visibility.modal" class="v--modal v--modal-box">`
Be careful with that, as I don't know what is the init value of visibility.modal.
I would use watch feature to know content height every time once visibility.modal gets true in this particular case.
Look at the example. It's based on the code which you provided and it's a possible solution in your case.
Modal.vue
data() {
return {
isMounted: false,
modalHeight: 0,
visibility: {
modal: false
}
}
},
mounted() {
this.isMounted = true;
},
watch: {
visibility: {
handler(value) {
if (value.modal && this.isMounted) {
this.modalHeight = this.$refs.modal.clientHeight;
}
},
deep: true
}
}
As you can see - you will always have actual modalHeight value and you can use it whenever you want within the Modal.vue component.

Related

Range Slider Nuxt JS

So I have a task, I need to build a calculator based on a range slider in Nuxt that changes the color when the thumb moves and it calculates something at the same time. I've managed to get it to work on a certain level. But when I flip pages, it crashes saying that can't read addEventListener of undefined.
here is the code:
<div class="range">
<input v-html="amount" v-model="value" type="range" class="slider" id="amount" min="0" max="100">
</div>
methods: {
colorSlider(){
const slider = document.querySelector('#amount')
let x = slider.value
let color = 'linear-gradient(90deg, rgb(249,84,78)' + x+ '%, rgb(224,224,224)' + x +'%)'
slider.style.background=color
}
},
mounted(){
document.querySelector('#amount').addEventListener('mousemove',this.colorSlider)
}
Any ideeas ?
You don’t need an event listener— Vue is reactive, simply tying the slider to a data property via v-model is fine.
Eg:
...
<input type="range" v-model="sliderValue">
...
export default {
data() {
return {
sliderValue: 0
}
}
}
Now when you adjust the slider, the value of sliderValue will update. No event listener required.
To use the value of sliderValue for something useful in your template— you should use a computed property, not a method.
export default {
data() {
return {
sliderValue: 0
}
},
computed: {
sliderBgColor() {
return `linear-gradient(90deg, rgb(249,84,78) ${this.sliderValue}%, rgb(224,224,224) ${this.sliderValue}%)`
}
}
}
Now when you adjust the slider, the data property it’s tied to (sliderValue) via v-model will change. The computed property sliderBgColor notices the change and automatically updated it’s return value. Use the return value of sliderBgColor in your input and you’re done.
<input type="range" v-model="sliderValue" :style="`background: ${sliderBgColor}`"
There’s plenty of information available on computed properties, I’d recommend taking a look at the Vue docs.

How does this `layout` prop get updated without `:layout.sync`?

I don't understand how vue-grid-layout manages to change myLayout in the following code sandbox:
<grid-layout
:layout="myLayout"
....
>
I thought that:
<MyComponent :layout="myLayout"/>
Meant that any changes to myLayout here in the parent scope would become changes in in the layout prop in MyComponent, but MyComponent would not be able to change myLayout.
Which is what the sync modifier was for, so that:
<MyComponent :layout.sync="myLayout"/>
Would be the equivalent of:
v-bind:layout="myLayout"
v-on:update:layout="myLayout = $event"
And so without the .sync, it would not be possible for a component to change a prop in the parent scope.
But the code sandbox demonstrates that vue-grid-layout manages to change myLayout using just :layout="myLayout".
(:layout.sync="layout" is used in the README code for vue-grid-layout and then I understand how it works, but it works without the .synctoo, which I don't understand.)
What am I missing or misunderstanding?
In javascript objects are passed around by reference, if you must prop an object, clone it and manipulate the clone to avoid it updating the original object.
for example:
props: {
myLayout: Object
},
data() {
return {
layout: {}
}
},
watch: {
myLayout: {
handler(myLayout) {
this.layout = { ...myLayout };
},
immediate: true
}
}

vue-konva: removing specific node from stage working incorrectly

I'm passing down an array of image urls as props to my Konva component, creating a image object for each url, storing that object in a data object (using Vue's $set method to keep the object literal reactive), then using v-for to create a v-image for each image object in my data object. This seems to be working fine, however I'm running into a problem where if I try to remove one of the images, 2 images will be removed. This only happens if the image that I try to remove is not the topmost image. In the console, I'm getting the warning Konva warning: Node has no parent. zIndex parameter is ignored.. My hunch is that this is a result of konva's destroy method clashing with vue's $delete method on a data object used in a v-for. I've been battling with this for hours, and would appreciate any help I can get. Relevant code is below. Thanks!
Parent
<template>
<editor ref="editor" :image-props.sync="images"/>
<button #click="remove">remove</button>
</template>
export default {
components: {
Editor,
},
data() {
return {
images: [url1, url2, etc...],
};
},
methods: {
remove() {
this.$refs.editor.removeSelectedImage();
},
}
child
<template>
<div>
<v-stage>
<v-layer>
<v-group>
<v-image v-for="image in Object.values(images)"
:key="image.id" :config="image"/>
</v-group>
</v-layer>
</v-stage>
</div>
</template>
export default {
props: {
imageProps: Array,
},
data() {
return {
images: {},
selectedNode: null //this gets updated on click
},
watch: {
imageProps() {
this.registerImages();
},
mounted() {
this.registerImages();
},
methods: {
registerImages() {
this.imageProps.forEach(url => {
if (!this.images[url]) {
let img = new Image();
img.src = url;
img.onload = () => {
this.$set(this.images, url, {
image: img,
draggable: true,
name: url,
x: 0,
y: 0,
});
}
}
});
},
removeSelectedLayer() {
let newImageProps = this.imageProps.filter(url => url !== this.selectedImageName);
this.$emit('update:image-props', newImageProps);
this.selectedNode.destroy();
this.$delete(this.images, this.selectedImageName);
this.stageArea.draw();
},
If I inspect the component in Vue devtools, the images object looks correct as well as imageProps, (even the Vue DOM tree looks right with the correct amount of v-images) however the canvas shows 1 less image than it should. Again, this only happens if I remove a image that wasn't initially on top. It seems to function fine if I remove the top-most image.
When you are developing an app with vue-konva it is better not to touch Konva nodes manually (there only rare cases when you need it, like updating Konva.Transformer).
You don't need to call node.destroy() manually. Just an item for your data.
From your demo, I see you are not using key attribute (you are using image.id for that, but it is undefined). It is very important to use keys in such case.
Updated demo: https://codesandbox.io/s/30qpxpx38q

Add focus to first input on load and next input after success using vue

I am creating a fill in the blank using vue. I have three components, a parent component where I build the question, an input component where I validate and the text component. I have stripped out a lot of the code to try and keep the question relevant, anything commented out is unsuccessful. Hope I am not out in left field on this one but have never attempted this so thought I would try here.
First issue:
Some of the questions have two inputs and I want to auto provide focus to the first input, (using a custom directive ) I am able to gain focus on the last created input. I am not really sure how to access first child I guess. Works well with single input questions though. I have tried doing so by using $refs and $nextTick(()) but no luck.
Second issue:
Once one input gets isCorrect I want to auto focus to the next available non correct element. I figured I would have be able to access the child input from the parent component and have been trying using this link but I have been unsuccessful.
Any help or insights is much appreciated. thanks
What it looks like for visual
What I am after
Parent Component
import ivInput from "../inline-components/iv-input.vue";
import ivText from "../inline-components/iv-text.vue";
<component
:is="buildQuestion(index)"
ref="ivWrap"
v-for="(item, index) in questionParts"
:key="index">
</component>
export default() {
components:{
ivInput,
ivText
},
mounted(){
// console.log(this.$refs.ivWrap)
// console.log(this.$refs.iv)
},
methods: {
buildQuestion: function (index) {
if(this.questionParts[index].includes('*_*')){
return ivInput
}else{
return ivText
}
},
//focus: function (){
// this.$refs.iv.focus()
// console.log(this.$refs.iv)
// }
}
}
Input Component
<div :class="'iv-input-wrap'">
<input
ref="iv"
v-focus
type="text"
v-model="userInput"
:class="[{'is-correct':isCorrect, 'is-error':isError}, 'iv-input']"
:disabled="isCorrect">
</div>
export default{
// directives:{
// directive definition
// inserted: function (el) {
// el.focus()
// },
}
computed{
isCorrect: function () {
if(this.isType == true && this.isMatch == true){
// this.$refs.iv.focus()
// this.$nextTick(() => this.$refs.iv.focus)
return true
}
}
}
}

Vuejs transition duration auto?

I'm using the Vuejs official router, and am currently working on giving different routes different animations, like in the example here: https://router.vuejs.org/en/advanced/transitions.html (see 'Route-Based Dynamic Transition')
My problem is that some transitions need a specific transition durations, and others don't (those I want to specify in the CSS).
I've made a variable that holds the duration to pass to the router, (same as this.transitionName in the router) but I was wondering if there was a way to set this variable to 'auto', for the routes that don't need a duration?
I don't know enough about your setup, so I'll only address modifying the transition duration.
As you can tell, the transitions are using css to manage the transitions.
I've taken the example in the doc you linked (https://github.com/vuejs/vue-router/blob/dev/examples/transitions/app.js) and added the functionality to override the duration.
const Parent = {
data() {
return {
transitionName: "slide-left",
transitionDuration: 2.5
};
},
computed: {
transitionStyle() {
return {
'transition-duration': this.transitionDuration +'s'
}
}
},
beforeRouteUpdate(to, from, next) {
const toDepth = to.path.split("/").length;
const fromDepth = from.path.split("/").length;
///////////////
// this is where you set the transition duration
// replace with your own implementation of setting duration
this.transitionDuration = Math.random()*1.5+0.5;
///////////////
this.transitionName = toDepth < fromDepth ? "slide-right" : "slide-left";
next();
},
template: `
<div class="parent">
<h2>Parent</h2>
{{transitionStyle}}
<transition :name="transitionName">
<router-view class="child-view" :style="transitionStyle"></router-view>
</transition>
</div>
`
};
This will override the duration by in-lining the style
You would still need to find a way to get the intended duration within beforeRouteUpdate, but I'll leave that up to you.
you can have a look at a working pen here: https://codepen.io/scorch/pen/LOPpYd
Note that the effect is only applied to the parent component, so animation on home(/) will not be affected