Access to a slot function inside a child component in VusJS - vue.js

I'm trying to use tiptap. Actually it works, but what I'm trying to do is to access the "isActive" slot from outside the editor component, and I don't know how to do it.
Here is a codesandbox example: https://codesandbox.io/s/v07xnxo807?file=/src/App.vue
You see the Editor component is called from the App.vue. The buttons in the Editor component are activated depending on the "isActive" slot functions.
What I would like is to access this slot to get for example the value of isActive.bold() from the App.vue, in order to update the model of a "multiple button" you can find on Vuetify: https://vuetifyjs.com/fr-FR/components/button-groups/
Here is for example what I could have:
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
<v-btn-toggle
v-model="toggle_multiple"
dense
background-color="primary"
dark
multiple
class="my-2"
>
<v-btn :color="isActive.bold()?'red':'green'" #click="commands.bold">
<v-icon>mdi-format-bold</v-icon>
</v-btn>
<v-btn #click="commands.italic">
<v-icon>mdi-format-italic</v-icon>
</v-btn>
<v-btn #click="commands.strike">
<v-icon>mdi-format-strikethrough</v-icon>
</v-btn>
<v-btn #click="commands.underline">
<v-icon>mdi-format-underline</v-icon>
</v-btn>
<v-btn #click="commands.blockquote">
<v-icon>mdi-format-quote-open</v-icon>
</v-btn>
</v-btn-toggle>
</editor-menu-bar>
And the toggle_multiple would be computed depending on the different "isActive" function values.
I already tried:
computed: {
toggle_multiple: function () {
let t = []
if (editor) {console.log("Bold: "+editor.isActive.bold())}
return t
}
},
But I receive this error: error 'editor' is not defined
I'm open to any suggestion.
Thanks in advance.

Property isActive is stored in the tiptap instance (in your case it's this.editor):
In HTML:
<div>{{editor.isActive.bold()}}</div>
In JS:
<div>{{toggle_multiple}}</div>
computed: {
toggle_multiple () {
// Keep in mind, other properties like ".isActive.heading()" will be undefined
// until you import the extension for it.
// So the function "heading" below exists only if you're using that extension
// console.log(this.editor.isActive.heading({ level: 2 })
return this.editor.isActive.bold()
}
}

Related

Close a Vuetify v-dialog in a custom component when using v-slot:activator

I am trying to create a custom component that wraps a Vuetify v-dialog.
I am having trouble with closing the dialog using a button inside the dialog.
I've tried many things, like #emit('input', false), this.value = false or using a local data value instead of value, but nothing seems to work.
My dialog looks like this (simplified):
// file: DeleteDialog.vue
<template>
<v-dialog :value="value" #input="$emit('input', $event)" width="550">
<template v-slot:activator="{ on, attrs }">
<slot
name="activator"
v-bind:on="on"
v-bind:attrs="attrs"></slot>
</template>
<v-btn #click="closeDialog">
Close
</v-btn>
</v-dialog>
</template>
<script>
export default {
props: {
value: Boolean,
},
methods: {
closeDialog() {
this.$emit('input', false);
},
},
};
</script>
When using the dialog like this, how do I get the Close-button to work?
<DeleteDialog>
<template v-slot:activator="{on, attrs">
<v-btn v-on="on" v-bind="attrs">
Show dialog
</v-btn>
</template>
Are you sure you want to delete this user?
</DeleteDialog>
The prop you pass value is responsible for showing or hiding your v-dialog inside DeleteDialog.vue.
So when clicking the button close, we will emit an event close that will maake the parent of DeleteDialog change the prop value it passed to it as false.
// in your parent component
<DeleteDialog :value="show_dialog" #close="show_dialog = false>
in your deleteDialog
//in delete dialog
<template>
<v-dialog :value="value" width="550">
<v-btn #click="closeDialog">
Close
</v-btn>
</v-dialog>
</template>
<script>
export default {
props: {
value: Boolean,
},
methods: {
closeDialog() {
this.$emit('close');
},
},
};
</script>
I think that is more suitable way of achieving what you want

Vue js - How to use props in data and methods

I am new in vue js , I am passing data from parent component to child one using props and I can use it in child normally but I can't use it in data of child component
parent
<template>
<div>
<show-question :qdata="question" v-if="question"></show-question>
<h1 v-if="!question"> some error </h1>
</div>
</template>
<script>
import ShowQuestion from './ShowQuestion';
export default {
created(){
axios.get(`/api/question/${this.$route.params.slug}`)
.then(res => {
this.question = res.data.data
})
},
data(){
return {
question : {},
}
},
components:{
ShowQuestion,
},
}
</script>
child
<template>
<v-container>
<v-card>
<div>
<v-card-title class="blue--text"
>{{ data.title }}
<v-spacer></v-spacer>
<v-btn color="teal white--text">5 Replies</v-btn>
</v-card-title>
<v-card-subtitle
> {{data.uid}} {{ data.user }} said {{ data.created_at }}</v-card-subtitle
>
</div>
<v-card-text>{{ data.body }}</v-card-text>
<v-card-actions v-if="own">
<v-btn icon text>
<v-icon color="orange">create</v-icon>
</v-btn>
<v-btn icon text>
<v-icon color="red">delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
<script>
export default {
props: ['qdata'],
data(){
return {
own: User.own(this.qdata.uid),
};
},
};
</script>
this.qdata.uid is always be undefined in console, although it supposed it have values and I can saw it from child template
enter image description here
Your show-questioncomponent is mounted early on because v-if="question" is true. When show-question is mounted, your api call hasn't had a chance to finish, so question is the same as the initial value {}, which is why uid is undefined.
I would change question : {} to question: null, then the child component will only be mounted when there's a question (after the api call).
This is simply because if you check the truthy of an object it will always return true even if the object is empty, which results in the component being rendered before the API call has finished.
Instead, you can check if it's empty or not by converting it to an array and check it's length value, i.e. Object.entries(question).length or simply use the lodash helper method: _isEmpty(question).
Also a quick side note: it's cleaner to use v-else after v-if when you want to render something or the other instead of explicitly negating the value in another v-if, though they're required to be direct siblings.

How to use another method's variable in a vue component?

I have two methods in a vue component.
First makes the user choose from a v-select, either itemone or itemtwo. Then, to retreive the value for later i call #change to assign the variable to a method declared later - getItemValue.
Second is a submit button, when clicked, we go to handleSubmit.
After handleSubmit is called, I want to use the value I got from getItemValue (in variable theItem), but how can I call another method if it's out of my scope?
Mycomponent.vue
<template>
<v-form
ref="form"
v-model="valid"
lazy-validation
>
<v-select
v-model="select"
:items="items"
#change="getItemValue"
></v-select>
<v-btn
#click="handleSubmit"
>
Submit
</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
items: [
'itemone',
'itemtwo'
],
}),
methods: {
getItemValue(theItem) {
},
handleSubmit(e) {
e.preventDefault()
// i need "theItem" here!
}
},
}
</script>
v-model already writes to your local variable, so there is absolutely no need to setup a get method to write the select value to a variable.
Actually, v-model is a bit more complicated than just 'write' to a variable, but the important bit is that in your template you are setting up v-model="select", which basically means that whenever the user uses the select to pick a value, your local select variable will be updated with the selected value.
Now, there is no select in your example component data, I don't know why. But if you had it, you could just sent that variable in your handleSubmit:
<template>
<v-form
ref="form"
v-model="valid"
lazy-validation
>
<v-select
v-model="select"
:items="items"
></v-select>
<v-btn
#click="handleSubmit"
>
Submit
</v-btn>
</v-form>
</template>
<script>
export default {
data: () => ({
select: '',
items: [
'itemone',
'itemtwo'
],
}),
methods: {
handleSubmit(e) {
e.preventDefault()
doSomethingWith(this.select); // this will be updated at this point
// with the option the user selected
}
},
}
</script>
Now, however, be aware that if the select variable is a component prop, then you should not do this right away, since props are not intended to be modified directly by child components. If that would be the case, please update your question with more info.
You would simple set the variable (theItem) value to the data
getItemValue(theItem) {
this.theItem;
},
and then retrieve it later
handleSubmit(e) {
e.preventDefault()
// i need "theItem" here!
// simple access theItem
console.log('theItem', this.theItem);
}

Switch Vuetify navigation drawer to mini and then temporary

I have a project in Vue.js and I am using Vuetify. I have a toolbar and navigation drawer. What I would like is when on desktop the drawer is open. If the user clicks the side-icon the drawer switches to mini.
If on md the drawer switches to mini. if the user clicks the side-icon the mini switches back to drawer
If on sm or lower the navigation drawer switches to temporary
I have most of the pieces but I am getting an error when I click the side-icon. Computed property 'mini' was assigned to but it has no setter.
Here is my code:
<v-toolbar
:clipped-left="$vuetify.breakpoint.mdAndUp"
:app="$vuetify.breakpoint.mdAndUp"
:fixed="$vuetify.breakpoint.mdAndUp"
flat
fixed
:scroll-toolbar-off-screen="$vuetify.breakpoint.smAndDown"
:scroll-threshold="50">
<v-toolbar-side-icon #click.stop="mini = !mini">
</v-toolbar-side-icon>
<v-toolbar-title class="text-uppercase">
<span class="font-weight-light">LOGO</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items class="">
<v-btn icon v-for="item in menu" :key="item.icon">
<v-icon>{{item.icon}}</v-icon>
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-navigation-drawer
clipped
:mini-variant="mini"
v-model="drawer"
:permanent="$vuetify.breakpoint.mdOnly"
:temporary="$vuetify.breakpoint.smAndDown"
app
hide-overlay>
<v-list dense>
<v-list-tile
v-for="(item, index) in items"
:key="index"
>
<v-list-tile-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-navigation-drawer>
I have created a codepen with what I have so far:
https://codepen.io/jsd219/pen/gJJMPQ
You're trying to assign a computed property to itself:
#click.stop="mini != mini"
You really don't want to do that. To find out why, you want to read on JS setters and getters.
If you want mini to be the computed which determines if your <navigation-drawer> is minified or not, use two separate placeholders for your info:
one for whether the menu is forcefully opened (call it menuOpen), initially defined in data(), as false and then overwritten by your #click.stop="menuOpen != menuOpen"
and one coming from $vuetify.breakpoint.mdAndUp. Call it mdAndUp.
So your mini becomes:
mini() {
return !(this.mdAndUp|| this.menuOpen);
}
See it here.
The problem is that you are trying to modify the computed property mini.
To mutate a computed property, you would need to provide a computed setter:
computed: {
mini: {
get() {
// get logic
},
set(value) {
// set logic
}
}
}
In your case, your computed property mini returns true or false if based on $vuetify.breakpoint.mdAndDown. You would need to include a new variable, something like overwriteBreakpoint and use that in your setter.
data() => ({
overwriteBreakpoint: true
}),
computed: {
mini: {
get() {
return this.$vuetify.breakpoint.mdAndDown || this.overwriteBreakpoint;
},
set(value) {
this.overwriteBreakpoint = value;
}
}
}
Here's an example: https://codepen.io/dormenog/pen/MddbMY?editors=1011
Update:
To make this work on multiple screen sizes you'll need to come up with defined rules on when each prop of your nav bar should be true or false. This will become very messy, very quickly, and the benefit of the keeping track of the state is not really valuable because the screens will not change size in real time on the user's device.
I would advice separating the contents of your navbar into a component and wrap it with multiple <v-navigation-drawer /> that will only be rendered by vue if the screen size is correct. This can be achieved using v-if and v-else.

Vue Change icon to click on the button

how can I change the icon when I click on the button in vue.
Here is a portion of the code:
<v-btn flat icon color="white">
<v-icon>star_border</v-icon>
</v-btn>
Thanks
<v-btn #click="show = !show" icon>
<v-icon>{{ !show ? 'mdi-eye' : 'mdi-close' }}</v-icon>
</v-btn>
add to component
{
data(){
return {
show:false
}
}
}
Hi Enzo and congrats on starting your VueJS project.
I would recommend you looking at VueJS documentation about Data and Methods, to give you some start. https://v2.vuejs.org/v2/guide/instance.html#Data-and-Methods
In short Data is where you keep your reactive properties and Methods store your functionality.
Right now the name of the icon is hard-coded. What you want to do is to make it reactive. So to change the icon;.
You need to bind the name of the icon to a property in your data.
Define a method that changes the value of that property.
Make an on-click event to invoke a function.
Something like this:
new Vue({
el: '#app',
data() {
return {
myIcon: 'star_border'
}
},
methods: {
changeIcon() {
this.myIcon = 'home'
}
}
})
Here I've defined a property called myIcon, which is 'star-border' at first. I've also created a method that gets invoked on the click-event of this button. This method changes the value of the myIcon property to 'home'.
You can see a working demo here: https://codepen.io/bergur/pen/MLMxzY