Div doesn't appear after using v-if statement - vue.js

<div class="min-h-screen flex" ref="mapDiv"></div>
<div class="bg-white absolute top-0 w-full overflow-y-auto"></div>
This is how it looks
Whenever I add to this div v-if
<div class="bg-white absolute top-0 w-full overflow-y-auto" v-if="settingsToggled"></div>
The div doesn't show up at all, even after being triggered to true. Screenshot:
I am returning settingsToggled in the setup function.
This is the code that triggers it:
export function AvatarControl(controlDiv, settingsToggled) {
const controlUI = document.createElement("div");
controlUI.style.width = "100px";
controlUI.style.margin = "10px"
controlUI.style.height = "100px";
controlUI.style.borderRadius = "8px"
controlUI.style.backgroundImage = "url('https://www.pngkey.com/png/full/114-1149878_setting-user-avatar-in-specific-size-without-breaking.png')"
controlUI.style.backgroundPosition = "center"
controlUI.innterHTML = "Avatar";
controlUI.style.zIndex = "9999";
controlDiv.appendChild(controlUI);
controlUI.addEventListener("click", () => {
console.log(settingsToggled)
settingsToggled = !settingsToggled
})
}
Anyone knows what might be the issue here?
Can I not modify the settingsToggled from outside this component?

In AvatarControl, settingsToggled appears to be a Boolean (based on settingsToggled = !settingsToggled), while your screenshot shows it as a ref, so I'm guessing you are passing the ref's value to AvatarControl like this:
// MyComponent.vue
new AvatarControl(controlDiv, settingsToggled.value)
But Booleans are passed by value, so the function can't modify the original variable like that. However, the function can modify the value of a ref argument:
// MyComponent.vue
new AvatarControl(controlDiv, settingsToggled)
// AvatarControl.js
export function AvatarControl(controlDiv, settingsToggled) {
const controlUI = document.createElement("div");
//...
controlUI.addEventListener("click", () => {
settingsToggled.value = !settingsToggled.value
})
}
}

You need a component data property for settingstoggled otherwise vue v-if hook will not find it.
Try like this in your .vue file:
data () {
return {
settingstoggled: true
}
}
You can find more info on component data here.

Related

VueMapbox trying to create multiple markers

I am trying to create multiple markers in Vue using VueMapbox. Currently the map displays correctly but there is only one marker. I think there is something wrong either with my v-for statement or perhaps in the forEach statement. I am trying to place a marker on each location but only the first location is added.
Here is the code for my vue component:
<template>
<MglMap
:accessToken="accessToken"
:mapStyle.sync="mapStyle"
>
<MglMarker v-for="coordinate in coordinates" :key="coordinate" :coordinates="coordinates">
<MglPopup>
<VCard>
<div>{{ country }}</div>
<div>{{ cases }}</div>
</VCard>
</MglPopup>
</MglMarker>
</MglMap>
</template>
<script>
import Mapbox from "mapbox-gl";
import { MglMap, MglPopup, MglMarker } from "vue-mapbox"
export default {
name: 'Map',
components: {
MglMap,
MglPopup,
MglMarker,
},
data() {
return {
accessToken: 'pk.accesstoken...blahblahblah',
mapStyle: 'mapbox://styles/mapbox/dark-v10',
coordinates: [],
country: '',
cases: 0,
}
},
created() {
this.mapbox = Mapbox;
this.getData();
},
methods: {
getData: function () {
fetch('https://coronavirus-tracker-api.herokuapp.com/v2/locations')
.then(response => response.json())
.then(data => {
const locations = data.locations;
locations.forEach(stat => {
const country = stat.country;
this.country = country;
const cases = stat.latest.confirmed;
this.cases = cases;
const coordinates = [stat.coordinates.longitude, stat.coordinates.latitude]
this.coordinates = coordinates;
})
})
}
}
}
</script>
You're currently doing a v-for on coordinates. It should be on locations.
If locations don't have all the required props a MglMarker needs, transform them in the forEach but that's all you should do in that forEach (if you need it at all). Don't use it to populate this.country, this.cases or this.coordinates. You only want to set those when a marker is clicked (if, and only if, you have any functionality listening to changes on those Vue instance properties).
There might be more details which need to be fixed but, without a minimal reproducible example it's very difficult to spot them. Note: you'll need to create a mapbox public token with readonly permissions for your example to work.
To summarize: Move the functionality from your forEach into a function called showMarker or activateMarker. Call that function whenever a marker is clicked or, if that's what you want, call it on one of the locations to make it the currently active one.
What your code does now is: it makes all markers the currently active one, therefore only the last one iterated will be currently active.
Here's what your MglMarker iterator might look like:
<MglMarker v-for="(l, key) in locations"
:key="key"
:coordinates="l.coordinates"
#click="activateMarker(l)"
>
<MglPopup>
VCard>
<div>{{ l.country }}</div>
<div>{{ l.latest.confirmed }}</div>
</VCard>
</MglPopup>
</MglMarker>
In activateMarker method you can dispatch any action to let the rest of the app know about your selection. Or you can close any other open popups, for example (although that can be handled easier with :closeOnClick="true" on each MglPopup).

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

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.

VueJS 2 - Listen to event in mixin

I'm currently trying to create a mixin for Vue which basically creates a property passthrough chain. I'll clarify what should happen to be a little more clear;
Let's say I got 3 components; A,B and C.
A & B are both the same component called 'content-pane' (See below for template code).
<div class="pane-wrapper">
<div class="content-pane" :class="{'is-hidden' : !active}" :content="name">
<div class="card white">
<div class="card-title grey darken-3">
<h1 class="white-text">{{ label }}</h1>
</div>
<div class="card-content white">
<component
:is = "type"
:routes = "routes"
:passthrough = "passthrough"
keep-alive
></component>
</div>
</div>
</div>
<content-pane
v-for="(pane, key) in children"
:key = "key"
:label = "pane.label"
:name = "pane.name"
:active = "true"
:type = "pane.type"
:routes = "pane.routes"
></content-pane>
</div>
C is a dynamic component, meaning that it is interchangeable and could be any component.
Now I want to be able to access certain data from component C in component A, and for that I am trying to create a mixin that dyamically offers a data property to do this:
<script>
export default {
name: 'passthrough',
props: {
passthrough : {
type : Object
}
},
data ()
{
return {
// This object allows you to
// update the parent.
passthroughModifier : {
// We use the data object inside the
// original object because Vue doesn't
// want to detect direct prop changes
// when they are added dynamically
// into the root object...
data : {}
}
}
},
methods : {
/**
* This function fires an emit event.
*/
emitUpdate ()
{
this.$emit('passthrough-update', this.passthroughModifier);
}
},
watch : {
/**
* Emit an event once the passthrough
* property has been changed.
* We need to use a deep watcher.
*/
'passthroughModifier' : {
handler : function (val) {
this.emitUpdate();
},
deep: true
}
},
created ()
{
// Allow access to the instance
// inside the iteration.
let _that = this;
// Attach a listener for the passthrough update
// which will walk through all the keys in the
// data object and hard-set these locally.
this.$on('passthrough-update', function (data) {
Object.keys(data).forEach(function (index) {
_that.passthroughModifier[index] = data[index];
});
});
}
}
Everything works fine except listening to the 'passthrough-update' event, which is fired by the watcher on $.passthroughModifier.
So; When component C updates its $.passthroughModifier.data, the event gets emitted, but component B isn't able to catch this event.
I have tried to listen for this event in the created() method of the mixin (see code above), but it seems as if the event only gets caught in the component the event is fired from. So component C fires the event, and component C listens to its own event.
I hope someone is able to tell me wether this is actually possible or not, and what I'm doing wrong if it is possible.

vue custom emit doesnt work in v-for

I have a component that looks like this:
<task-list :content="lesson.children" :iterator="lesson.section" :user="user" #updateTask="updateTask"></task-list>
I use component in 2 different pages. In on page its inserted just as is, in another page its parent is a v-for so it looks like:
<div class="lesson-list-item" v-bind:class="{ 'lesson-complete': course.iterator_position > index}" v-for="(lesson, index) in content" >
<task-list :content="lesson.children" :iterator="lesson.section" :user="user" #updateTask="updateTask"></task-list>
</div>
Both pages have the method:
updateTask(index){
var self = this;
this.loaded = false;
this.iterator = index;
this.newTask = index;
setTimeout(function() { self.loaded = true }, 10);
}
This is what task-list emits on a click:
cardClick(index, slug, initialIterator){
console.log(index);
if( index <= this.initialIterator){
this.$emit('updateTask', index);
}
}
The page where its not wrapped in a v-for works, the one where it is never receives the emit event.
I think your component doesn't emit an updateTask event.
Add this to the method that you want to fire the event: this.$emit('updateTask');

Binding method result to v-model with Vue.js

How do you bind a method result to a v-model with Vue.js?
example :
<someTag v-model="method_name(data_attribute)"></someTag>
I can't make it work for some reason.
Thank you.
Years later, with more experience, I found out that is it easier to bind :value instead of using v-model. Then you can handle the update by catching #change.
Edit (per request):
<input :value="myValue" #change="updateMyValue">
...
methods: {
updateMyValue (event) {
myValue = event.target.value.trim() // Formatting example
}
}
And in a child component:
// ChildComponent.vue
<template>
<button
v-for="i in [1,2,3]">
#click="$emit('change', i) />
</template>
// ParentComponent.vue
<template>
<child-component #change="updateMyValue" />
</template>
<script>
import ChildComponent from './child-component'
export default {
components: {
ChildComponent
},
data () {
return {
myvalue: 0
}
},
methods: {
updateMyValue (newValue) {
this.myvalue = newValue
}
}
}
</script>
v-model expressions must have a get and set function. For most variables this is pretty straight forward but you can also use a computed property to define them yourself like so:
data:function(){
return { value: 5 }
},
computed: {
doubleValue: {
get(){
//this function will determine what is displayed in the input
return this.value*2;
},
set(newVal){
//this function will run whenever the input changes
this.value = newVal/2;
}
}
}
Then you can use <input v-model="doubleValue"></input>
if you just want the tag to display a method result, use <tag>{{method_name(data_attribute)}}</tag>
Agree with the :value and #change combination greenymaster.
Even when we split the computed property in get/set, which is help, it seems very complicated to make it work if you require a parameter when you call for get().
My example is a medium sized dynamic object list, that populates a complex list of inputs, so:
I can't put a watch easily on a child element, unless I watch the entire parent list with deep, but it would require more complex function to determine which of the innter props and/or lists changed and do what fromthere
I can't use directly a method with v-model, since, it works for providing a 'get(param)' method (so to speak), but it does not have a 'set()' one
And the splitting of a computed property, have the same problem but inverse, having a 'set()' but not a 'get(param)'