Mouseover function wont toggle - vue.js

I am trying to change the hover value on hover, but it doesn't seem to work whenever I hover over the flex element, what am I doing wrong?
html
template(#default="{ toggle, toggled, hover }")
.flex(#click="toggle" #mouseover="hover = !hover" )
box-icon(type="solid" name="chevron-down" v-if="hover")
component
<template lang="pug">
slot(:toggled="toggled" :toggle="toggle" :hover="hover")
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const toggled = ref(false)
const hover = ref(false)
function toggle() {
toggled.value = !toggled.value
}
return { toggled, toggle, hover }
}
}
</script>

#mouseover is not an event in vuejs.
You will have to use #mouseenter and #mouseleave.
Also it seems like you cannot edit a ref value, you will have to create a function in your template that changes that value. That is something I cannot explain myself right now. Nevertheless I made it work.
Here is a working example :
// Test.vue component
<script setup>
const toggled = ref(false)
const hovered = ref(false)
const hover = (value) => hovered.value = value
const toggle = () => toggled.value = ! toggled.value
</script>
<template>
<slot :toggled="toggled" :toggle="toggle" :hovered="hovered" :hover="hover" />
</template>
<Test>
<template #default="{toggle, toggled, hover, hovered}">
<button #click="toggle" #mouseenter="hover(true)" #mouseleave="hover(false)">
<p>Toggled : {{ toggled }}</p>
<p>Hovered : {{ hovered }}</p>
</button>
</template>
</Test>

Related

Passing a value via prop, editing it and saving the new value vue 3

I am trying to pass a value into a child component. The child component will then preform the save operation. The parent doesn't need to know anything about it. I am able to pass in the object but not save its updated form.
Parent
<template>
<div v-show="isOpened">
<EditModal #toggle="closeModal" #update:todo="submitUpdate($event)"
:updatedText="editText" :todo="modalPost" />
</div>
</template>
<script setup lang="ts">
import Post from "../components/Post.vue";
import { api } from "../lib/api";
import { ref } from "vue";
import { onMounted } from "vue-demi";
import EditModal from "../components/EditModal.vue";
const postArr = ref('');
const message = ref('');
let isOpened = ref(false);
let modalPost = ref('');
let editText = ref('');
function closeModal() {
isOpened.value = false
}
function openModal(value: string) {
isOpened.value = true
modalPost.value = value
}
// call posts so the table loads updated item
function submitUpdate(value: any) {
console.log("called update in parent " + value)
editText.value = value
posts()
}
</script>
Child EditModal
<template>
<div>
<div>
<textarea id="updateTextArea" rows="10" :value="props.todo.post"></textarea>
</div>
<!-- Modal footer -->
<div>
<button data-modal-toggle="defaultModal" type="button"
#click="update(props.todo.blogId, props.todo.post)">Save</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { api } from "../lib/api";
import { reactive, ref } from "vue";
const props = defineProps({
todo: String,
updatedText: String,
})
const emit = defineEmits(
['toggle','update:todo']
);
function setIsOpened(value: boolean) {
emit('toggle', value);
}
function update(id: string, value: string) {
console.log('the value ' + value)
try {
api.updateBlog(id, value).then( res => {
emit('update:todo', value)
emit('toggle', false);
})
} catch (e) {
console.log('Error while updating post: '+ e)
}
}
</script>
I know the props are read only, therefore I tried to copy it I can only have one model.
I do not see the reason I should $emit to the parent and pass something to another variable to pass back to the child.
I am trying to pass in text to a modal component where it can edit the text and the child component saves it.
Advice?
First, your component should be named EditTodo no EditModal because you are not editing modal. All Edit components should rewrite props to new local variables like ref or reactive, so you can work on them, they won't be read only any more.
Child EditTodo.vue
<template>
<div>
<div>
<textarea id="updateTextArea" rows="10" v-model="data.todo.title"></textarea>
</div>
<div>
<button data-modal-toggle="defaultModal" type="button" #click="update()">Save</button>
</div>
</div>
</template>
<script setup lang="ts">
import { api } from "../lib/api";
import { reactive } from "vue";
const props = defineProps<{ id: number, todo: { title: string} }>()
const emit = defineEmits(['toggle']);
const data = reactive({ ...props })
// I assuming api.updateBlog will update data in database
// so job here should be done just need toogle false modal
// Your array with todos might be not updated but here is
// no place to do that. Well i dont know how your API works.
// Database i use will automaticly update arrays i updated objects
function update() {
try {
api.updateBlog(data.id, data.todo ).then(res => {
emit('toggle', false);
})
}
}
</script>
Parent
<template>
<div>
<BaseModal v-if="todo" :show="showModal">
<EditTodo :id="todo.id" :todo="todo.todo" #toggle="(value) => showModal = value"></EditTodo>
</BaseModal>
</div>
</template>
<script setup lang="ts">
const showModal = ref(false)
const todo = reactive({ id: 5, todo: { title: "Todo number 5"} })
</script>
I separated a modal object with edit form, so you can create more forms and use same modal. And here is a simple, not fully functional modal.
<template>
<div class="..."><slot></slot></div>
</template>
<script setup>
defineProps(['show'])
defineEmits(['toogle'])
</script>
You might want to close modal when user click somewhere outside of modal.

Passing the default value of props to data in #Component

Having the code, I am getting State: undefined and clicking the toggle button does nothing.
How to get the default value from #Prop to some data correctly using #Component?
<script lang="ts">
import {
Component,
Prop,
Vue,
} from 'vue-property-decorator';
#Component
export default class HelloWorld extends Vue {
#Prop({ type: Boolean, default: true })
expanded!: boolean;
isExpanded = this.expanded;
// if its isExpanded = true; then toggling works.
}
</script>
<template>
<div class="greetings">
State: {{ isExpanded }}
<br />
<button #click="isExpanded = !isExpanded">Toggle</button>
</div>
</template>
You should not mutate the prop. Instead send a message to the parent, and let the parent mutate it.
<button #click="$emit('update:expanded', !expanded)">Toggle</button>
<hello-world :expanded='theValue' #update:expanded='theValue=$event' />

how to use suspense and dynamic components in vue3

In suspense, I import four different components asynchronously. When I click the button to switch, I find that loading slots in suspense will only be shown for the first time, but it doesn't work when I switch again. How to solve this problem? Does Suspense not support use with dynamic routing?
<template>
<div class="app">
<button #click="index = 0">1</button>
<button #click="index = 1">2</button>
<button #click="index = 2">3</button>
<button #click="index = 3">4</button>
<Suspense>
<component :is="component[index]"></component>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent, ref } from 'vue'
const son1 = defineAsyncComponent(() => import('./components/son1.vue'))
const son2 = defineAsyncComponent(() => import('./components/son2.vue'))
const son3 = defineAsyncComponent(() => import('./components/son3.vue'))
const son4 = defineAsyncComponent(() => import('./components/son4.vue'))
const component = [son1, son2, son3, son4]
const index = ref(0)
</script>
<style scoped lang="less"></style>
enter image description here
By default when a Suspense has been resolved it doesn't display the fallback content if the root component is changed. You can use the timeout prop of Suspense to make it display the fallback content if the component doesn't render before the timeout.
In your case a timeout of 0 will make sure that the fallback content is immediately displayed when the dynamic component changes :
<Suspense timeout="0">
<component :is="component[index]"></component>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>

How do I remove a Quasar q-input prop on button click?

I have a quasar q-input element that I want to enable and disable at the click of a button.
I have added a button to the input using v-slot:after but I don't know how to remove the disable prop when it is clicked.
P.S. I am using Quasar + Vue 3 + TypeScript.
<template>
<q-input
disable
type="text"
autocomplete="given-name"
label="First Name"
v-model="firstName"
>
<template v-slot:after>
<q-btn
label="Edit"
#click="handleClick"
/>
</template>
</q-input>
</template>
<script setup lang="ts">
import { ref, Ref } from 'vue';
const firstName: Ref<string | null> = ref(null);
const handleClick = () => {
//Remove the "disable" prop from the q-input element
}
</script>
In VUE you must think backwards.
Put in input :disable="myDisable"
Create the ref myDisable in the setup and put your initial value that you need true or false
In your handleClick change the value of myDisable =!myDisable

Teleport in component from slot Vue3

I want to create tabs component for my components library. I want tabs and tab components to work like this:
<b-tabs>
<b-tab
:title="'tab 1'"
:is-active="false"
>
tab content1
</b-tab>
<b-tab
:title="'tab 2'"
:is-active="false"
>
tab content2
</b-tab>
<b-tab
:title="'tab 3'"
:is-active="true"
>
tab content3
</b-tab>
</b-tabs>
So we have two components and they have some props including is-active which by default will be false.
The parent component - tabs.vue will be something like this
<template>
<section :class="mode ? 'tabs--light' : 'tabs--dark'" #change-tab="selectTab(2)">
<div :id="`tabs-top-tabId`" class="tabs__menu"></div>
<slot></slot>
</section>
</template>
here we have wrapper for our single tab which will be displayed here using slot. Here in this "parent" component we are also holding selectedIndex which specify which tab is selected and function to change this value.
setup () {
const tabId = Math.random() // TODO: use uuid;
const data = reactive<{selectedIndex: number}>({
selectedIndex: 0
})
const selectTab = (i: number) => {
data.selectedIndex = i
}
return {
tabId,
...toRefs(data),
selectTab
}
}
TLDR Now as you guys might already noticed in tab.vue I have div with class tabs__menu which I want to teleport some stuff into. As the title props goes into <tab> component which is displayed by the slot in tabs.vue I want to teleport from tab to tabs.
My tab.vue:
<template>
<h1>tab.vue {{ title }}</h1>
<div class="tab" v-bind="$attrs">
<teleport :to="`#tabs-top-tabId`" #click="$emit('changeTab')">
<span style="color: red">{{ title }}</span>
</teleport>
<keep-alive>
<slot v-if="isActive"></slot>
</keep-alive>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
title: {
type: String as PropType<string>,
required: true
},
isActive: {
type: Boolean as PropType<boolean>,
required: true
}
// tabId: {
// type: Number as PropType<number>, // TODO: change to string after changing it to uuid;
// required: true
// }
}
})
</script>
However this span does not get teleported. When I run first snippet for this post I can't see it displayed and I don't see it in DOM.
Why teleported span doesnt display?
I came across this issue recently when using element-plus with vue test utils and Jest.
Not sure if this would help but here is my workaround.
const wrapper = mount(YourComponent, {
global: {
stubs: {
teleport: { template: '<div />' },
},
},
})