Quasar: drawerClick not working when I click one of menus - javascript

I would like to have responsive drawer, so tried to code in accordance with Quasar Docs, however, it was not resized properly when the screen size is being changed. In order to solve this, I used computed in the script. I think due to this computed, the function drawerClick not working.
When the drawer is on minimode, and one of menus are clicked, then the menu should be expanded, but the problem I have is it never expanded.
template
<template>
<q-drawer
v-model="drawer"
show-if-above
:mini="!drawer || miniState || miniStatus"
#click.capture="drawerClick"
:width="220"
:breakpoint="500"
bordered
:content-style="{ backgroundColor: '#f5f7f9' }"
id="side-menu"
>
<q-scroll-area class="fit">
<q-list padding>
<q-btn v-if="!miniState" flat left class="logo-btn">
<img src="~assets/os_logo.png" width="144px" height="24px" contain />
</q-btn>
<q-btn v-else flat left>
<img src="~assets/os_logo_no_text.png" width="24px" contain />
</q-btn>
<q-expansion-item
default-opened
v-for="(menu, index) in menus"
header-class="header-bg text-black"
expand-icon-class="text-gray"
>
<q-expansion-item
v-for="(sub, index) in menu.subMenus"
:key="index"
:label="sub.title"
expand-icon="none"
class="sub-content"
:to="{ name: sub.link }"
/>
</q-expansion-item>
</q-list>
</q-scroll-area>
</q-drawer>
</template>
script
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'SideMenu',
data() {
return {
drawer: false,
miniStatus: false,
switchToMini: 700
};
},
computed: {
miniState: function() {
return this.$q.screen.width < this.switchToMini;
}
},
methods: {
drawerClick(e) {
if (this.miniStatus) {
this.miniStatus = false;
e.stopPropagation();
}
}
}
});
</script>
I explained better here in this image
[1]: https://i.stack.imgur.com/PqYoc.png

Related

Vue 3/Quasar: Handling opening and closing of modals

I have two modals:
One is a Sign Up modal which takes in information of an existing user. Another modal which allows an existing user to login.
The only way to get to the Login modal is through the Signup modal.
But what I would like to do is, if the use wants to open the Login, I would like to close the Sign up modal first.
Right now that seems impossible with my setup. With the code below (nextTick), it does not work and closes both modals... despite there being a unique v-model for each modal.
Sign up Modal
<template>
<AVModal
:title="$t('signup.create_an_account')"
:button-text="$t('signup.button_text')"
classes="hide-icon q-mt-sm"
modal-style="width: 350px"
v-model="modal"
>
<q-form
#submit="signUp(user)"
class="row column fitq-gutter-md q-gutter-md"
>
<q-input
outlined
v-model="signup_user.username"
:label="$t('signup.name')"
>
<template v-slot:append>
<q-icon name="person" color="grey" />
</template>
</q-input>
<q-input
outlined
v-model="signup_user.email"
type="email"
:label="$t('signup.email')"
>
<template v-slot:append>
<q-icon name="email" color="grey" />
</template>
</q-input>
<q-input
outlined
v-model="signup_user.password"
type="password"
:label="$t('signup.password')"
>
<template v-slot:append>
<q-icon name="lock" color="grey" />
</template>
</q-input>
<q-checkbox
v-model="signup_user.privacy_policy"
color="secondary"
:label="$t('signup.privacy_policy')"
/>
<q-checkbox
v-model="signup_user.newsletter"
color="secondary"
:label="$t('signup.newsletter')"
/>
<q-btn color="primary" class="q-py-sm" type="submit">{{
$t("signup.get_started")
}}</q-btn>
</q-form>
<div class="row q-my-sm q-mt-md fill">
<AVLoginModal #on-open="handleOpen" />
</div>
<!-- <AVSeperator text="or" /> -->
<!-- <AVSocialMediaButtons class="q-mt-md" /> -->
</AVModal>
</template>
<script setup>
import { ref, nextTick } from "vue";
import AVModal from "../atoms/AVModal.vue";
// import AVSeperator from "../atoms/AVSeperator.vue";
// import AVSocialMediaButtons from "../atoms/AVSocialMediaButtons.vue";
import AVLoginModal from "./LoginModal.vue";
import { useAuth } from "../../composables/useAuth";
const modal = ref(false);
const handleOpen = () => {
nextTick(() => (modal.value = false));
};
const { signUp, signup_user } = useAuth();
</script>
Login Modal:
<template>
<AVModal
:title="$t('login.welcome_back')"
:button-text="$t('signup.login_instead')"
classes="hide-icon q-mt-sm fit"
color="blue"
modal-style="width: 350px"
v-model="modal"
#on-open="emit('on-open')"
>
<q-form
#submit="login(login_user)"
class="row column fitq-gutter-md q-gutter-md"
>
<q-input
outlined
v-model="login_user.email"
type="email"
:label="$t('signup.email')"
>
<template v-slot:append>
<q-icon name="email" color="grey" />
</template>
</q-input>
<q-input
outlined
v-model="login_user.password"
type="password"
:label="$t('signup.password')"
>
<template v-slot:append>
<q-icon name="lock" color="grey" />
</template>
</q-input>
<q-btn color="primary" class="q-py-sm" type="submit">{{
$t("login.login")
}}</q-btn>
</q-form>
<!-- <AVSeperator text="or" class="q-my-md" /> -->
<!-- <AVSocialMediaButtons class="q-mt-md" /> -->
</AVModal>
</template>
<script setup>
import { ref, defineEmits } from "vue";
import { useAuth } from "../../composables/useAuth";
import AVModal from "../atoms/AVModal.vue";
// import AVSeperator from "../atoms/AVSeperator.vue";
// import AVSocialMediaButtons from "../atoms/AVSocialMediaButtons.vue";
const modal = ref(false);
const emit = defineEmits(["on-open"]);
const { login_user, login } = useAuth();
</script>
Maybe my design is bad? Is there a more conventional way of creating modals in Quasar?
Here is my reuseable Modal component:
<template>
<q-btn
:color="color"
:align="align"
flat
#click="openModal"
:icon="icon"
:class="[classes]"
:style="{ width }"
>{{ buttonText }}</q-btn
>
<q-dialog v-model="modal" :persistent="persistent">
<q-card :style="modalStyle">
<q-card-section>
<div class="row justify-between">
<div>
<div class="text-h6">{{ title }}</div>
<div class="text-subtitle2">
<slot name="subtitle" />
</div>
</div>
<slot name="top-right" />
</div>
</q-card-section>
<q-card-section class="q-pt-none">
<slot />
</q-card-section>
<q-card-actions align="right" class="bg-white text-teal">
<slot name="actions" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup>
import { computed, defineProps, defineEmits } from "vue";
const props = defineProps({
icon: {
type: String,
default: null,
},
color: {
type: String,
default: "",
},
title: {
type: String,
default: "",
},
buttonText: {
type: String,
default: "Open Modal",
},
modalStyle: {
type: String,
default: "width: 900px; max-width: 80vw",
},
width: {
type: String,
default: "",
},
align: {
type: String,
default: "around",
},
classes: {
type: String,
default: "",
},
persistent: {
type: Boolean,
default: false,
},
modelValue: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["on-open", "update:modelValue"]);
const modal = computed({
get: () => props.modelValue,
set: (val) => emit("update:modelValue", val),
});
const openModal = () => {
modal.value = true;
emit("on-open");
};
</script>
<style lang="scss">
.hide-icon {
i.q-icon {
display: none;
}
}
</style>
You can use the Dialog plugin with a custom component. By doing that, you can not just correctly open nested dialogs, but also simplify the prop/event management. Instead of a re-usable modal(AVModal.vue), you would create a re-usable card/container. Then, use that card in your Dialog plugin custom components.
Here is what closing the signup modal would look like in both approaches:
// Signup Modal
// Current way to close the modal (which doesn't correctly work)
modal.value = false; // v-model="modal"
// Custom dialog component way
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
onDialogCancel();
Instead of a button and a q-dialog inside the component, you would have the custom dialog component in a separate file, then when the button is clicked, invoke the Dialog plugin like this:
Dialog.create({
component: LoginModal,
})

Slot contents are merged between Tabs in VueJS

I have this TabPanel where I am rendering multiple tabs. Each Panel inside TabPanel uses/extends Panel where I am using slots. However, during tab switch content of Tabs are getting merged.
<template>
<div class>
<div class>
<ul class>
<li
style="display: inline-block; padding:10px; margin: 10px; border: 1px solid #ccc"
v-for="(panel, index) in panels"
:key="index"
#click="selectTab(index)"
:ref="'panel' + index"
>{{ panel.title }}</li>
</ul>
</div>
<div class>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: "TabPanel",
data() {
return {
panels: [],
selectedIndex: 0
};
},
methods: {
selectTab(panelIndex) {
this.selectedIndex = panelIndex;
this.panels.forEach((panel, index) => {
panel.isActive = index === panelIndex;
});
}
},
created() {
this.panels = this.$children;
},
mounted() {
this.selectTab(0);
}
};
</script>
Panel code
<template>
<div v-show="isActive">
<slot></slot>
</div>
</template>
<script>
export default {
name: "Panel",
props: {
title: {
type: String,
required: true
},
panelId: {
type: String,
required: true
}
},
data() {
return {
isActive: true
};
}
};
</script>
Is there anyway to fix this issue. This is the CodeSandbox link to demonstrate this issue since its easier to visualize problem that way I think.
Dashboard.vue and RosePanel.vue changes isActive data but they are not doing nothing whit this.
One option is to set a v-show/v-if to each own <Panel>:
<Panel v-show="isActive" :title="title" :panelId="panelId">Content from Dashboard</Panel>
CodeSandbox: https://codesandbox.io/s/charming-hamilton-jz1og
The simplest way to fix the issue is to wrap all components in <Panel>-s:
<TabPanel>
<Panel title="Dashboard" panel-id="dashboard">
<Dashboard></Dashboard>
</Panel>
<Panel title="Test" panel-id="test">Content from Panel</Panel>
<Panel title="Rose Panel" panel-id="rose-panel">
<RosePanel></RosePanel>
</Panel>
</TabPanel>
https://codesandbox.io/s/eager-brown-6ethk?file=/src/App.vue
But this is probably not what you had in mind.
From what I see, you simply forgot to propagate the "is-active" property to the embedded panel:
<Panel :title="title" :panelId="panelId" :is-isActive="isActive">Content from Dashboard</Panel>
(e.x in the Dashboard Component)

Vuetify combobox add option multiple times?

I am using the vuetify framework and I am running into this issue where I am not sure how I can add an item from the list multiple times. I have a dropdown list and I would like to add the option foo or any option multiple times on select. Here is a link to the demo codepen.
So right now if I select foo or any other option and then select it again from the dropdown list, it goes away, instead I want another chip with same option
added into it?
new Vue({
el: '#app',
data() {
return {
items: [{
text: 'Foo',
value: 'foo'
},
{
text: 'Bar',
value: 'bar'
},
{
text: 'biz',
value: 'buzz'
},
{
text: 'buzz',
value: 'buzz'
}
],
}
}
})
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<v-app id="inspire">
<v-container>
<v-combobox :items="items" label="Add Multiple Chips" multiple small-chips solo deletable-chips>
<template v-slot:item="{ index, item }">
<v-list-tile-content>
{{item.text}}
</v-list-tile-content>
</template>
<template v-slot:selection="{ index, item }">
<v-chip close dark color="info">
{{ item.text }}
</v-chip>
</template>
</v-combobox>
</v-container>
</v-app>
</div>
If anyone has any clue on how to achieve this. It will be much appreciated. Thank you
A couple of small adjustments,
put a .stop on the item click to prevent Vuetify from processing after your handler
tell the combo-box to use arr for :value
add a delete click handler to v-chip and corresponding method (NB this works on Vuetify 2.1.0, but not on Vuetify 1.5.14 as used on the Codepen. If you don't need that specific version, install the latest.
Codepen Vuetify v1.5.14
CodeSandbox Vuetify v2.1.0
<template>
<div id="app">
<v-app id="inspire">
<v-container>
<v-combobox
:items="items"
label="Add Multiple Chips"
multiple
small-chips
solo
deletable-chips
:value="arr"
>
<template v-slot:item="{ index, item }">
<v-list-tile-content #click.stop="multipleSelection(item)">{{item.text}}</v-list-tile-content>
</template>
<template v-slot:selection="{ index, item }">
<v-chip close dark color="info"
#click:close="deleteChip(item)" >{{ item.text }}</v-chip>
</template>
</v-combobox>
</v-container>
</v-app>
</div>
</template>
<script>
export default {
name: "playground",
data: () => ({
arr: [],
items: [
{
text: "Foo",
value: "foo"
},
{
text: "Bar",
value: "bar"
},
{
text: "biz",
value: "buzz"
},
{
text: "buzz",
value: "buzz"
}
]
}),
methods: {
multipleSelection(item) {
this.arr.push({...item});
console.log(this.arr);
},
deleteChip(item) {
this.arr = this.arr.filter(x => x !== item);
console.log(this.arr);
}
}
};
</script>
Addressing the problem of long selection lists being obscured by the dropdown menu,
The dropdown menu looks like this at runtime (via chrome developer tools)
<div class="v-menu__content theme--light menuable__content__active v-autocomplete__content"
style="max-height: 304px; min-width: 357px; top: 149px; left: 12px; transform-origin: left top; z-index: 8;">
<div role="listbox" tabindex="-1" class="v-list v-select-list v-sheet v-sheet--tile theme--light theme--light"
id="list-261">
<div tabindex="0" role="menuitem" id="list-item-267" class="v-list-item v-list-item--link theme--light">Foo</div>
<div tabindex="0" role="menuitem" id="list-item-268" class="v-list-item v-list-item--link theme--light">Bar</div>
<div tabindex="0" role="menuitem" id="list-item-269" class="v-list-item v-list-item--link theme--light">biz</div>
</div>
</div>
and has these styles
element.style {
max-height: 304px;
min-width: 357px;
top: 149px;
left: 12px;
transform-origin: left top;
z-index: 8;
}
Vuetify changes the top: 149px in it's selection handler, but since we turned that off we need to call updateMenuDimensions() in our own handler multipleSelection().
To do this, add a ref to the combobox,
<v-combobox
:items="items"
label="Add Multiple Chips"
multiple
small-chips
solo
deletable-chips
:value="arr"
ref="combobox"
>
...
</v-combobox>
Then add the call to updateMenuDimensions, inside a nextTick() to allow the selection box to settle.
methods: {
multipleSelection(item) {
this.arr.push({ ...item });
console.log(this.arr);
this.$nextTick(() => this.$refs.combobox.updateMenuDimensions())
},
deleteChip(item) {
this.arr = this.arr.filter(x => x !== item);
console.log(this.arr);
this.$nextTick(() => this.$refs.combobox.updateMenuDimensions())
}
}
Codepen Vuetify v1.5.14 (NB not deleting chips).
Preventing text from adding an item
There's another problem (for anyone following here but not seeing the chat room discussion). I've separated the answers for each issue, to make things easier to follow.
Feel free to vote on all answers :).
Problem
If the user types into the combobox, the expectation is that the list will be filtered, and it is.
But if the user presses enter after the text, an item is added and this is not desired.
We tried a permutations of event suppression, including,
#keydown.enter.prevent
#keypress.enter.prevent
#submit.prevent
#keydown.enter.prevent="enterItem" with event.preventDefault() in the handler
#blur.prevent
plus permutations with .stop, .capture.
None of the event suppression worked, so a hacky way to do it is to v-if the v-chip
<v-combobox
...various attributes as before
>
<template v-slot:item="{ index, item }">
...
</template>
<template v-slot:selection="{ index, item }">
<v-chip v-if="item.text" ...other attributes >{{ item.text }}</v-chip>
</template>
</v-combobox>
This works because the added item has no text (reason unknown), and the variable this.arr does not get modified when pressing the enter key as described.
Codepen

Vuetify overlay overlays the drawer

I'm trying to create a navbar for Vue application, I'm using Vuetify, on large screen there is toolbar, on small there is hamburger icon which opens the navigation drawer.
It works, but there is an overlay that comes in front of drawer and I can't change a page.
Here is the code:
<template>
<div>
<v-app-bar dark>
<v-app-bar-nav-icon class="hidden-md-and-up" #click="sidebar = !sidebar"></v-app-bar-nav-icon>
<v-navigation-drawer v-model="sidebar" app>
<v-list>
<v-list-item v-for="(item, i) in menuItems" exact :key="i" :to="item.path">{{item.title}}</v-list-item>
</v-list>
</v-navigation-drawer>
<v-toolbar-title>Jobify</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-sm-and-down">
<v-btn text v-for="item in menuItems" :key="item.title">
<router-link :to="item.path">{{item.title}}</router-link>
</v-btn>
</v-toolbar-items>
</v-app-bar>
</div>
</template>
<script>
export default {
data: function() {
return {
sidebar: false,
menuItems: [
{ path: "/jobs", name: "jobs", title: "Jobs" },
{ path: "/companies", name: "companies", title: "Companies" },
{ path: "/jobs/new", name: "new-job", title: "Add job" }
]
};
}
};
</script>
<style>
</style>
Only Vuetify components that I'm using are here, there is, actually, v-app in App component.
Try to add hide-overlay prop to the v-navigation-drawer component in order to hide the overlay :
<v-navigation-drawer v-model="sidebar" app hide-overlay >

Laravel + Quasar #click to show modal not working

I'm trying to use quasar for the first time and I am trying to show a modal with a button but somehow the value won't turn to true which triggers my modal to show.
Home.vue
<template>
<div>
<q-layout>
<q-list highlight class="bg-white">
<q-list-header>Details
<q-btn color="green-5" #click="openAdd">Add New</q-btn>
</q-list-header>
<!-- <q-search/> -->
<q-item>
<q-item-side>
<q-item-tile avatar>
<img src="https://cdn.quasar.dev/logo/svg/quasar-logo.svg">
</q-item-tile>
</q-item-side>
<q-item-main label="John Doe" />
<q-item-side right>
<div>
<q-btn flat round icon="edit" color="yellow-8" small/>
<q-btn flat round icon="delete" color="red-8" small/>
<q-btn flat round icon="visibility" color="green-6" small/>
</div>
</q-item-side>
</q-item>
</q-list>
</q-layout>
<Add></Add>
</div>
</template>
Home.vue(script)
<script>
let Add = require('./Add.vue');
export default {
name: 'app',
components: {Add},
data(){
return{
addActive : ''
}
},
methods: {
openAdd() {
this.addActive = '';
}
}
}
</script>
Add.vue(modal):
<template>
<div>
<q-modal v-model="addActive" ref="layoutModal" :content-css="{minWidth: '50vw', minHeight: '50vh'}">
<q-modal-layout>
<q-toolbar slot="header" color="green-7">
<div class="q-toolbar-title">
Header
</div>
</q-toolbar>
<q-toolbar slot="footer" color="green-7">
<div class="q-toolbar-title">
Footer
</div>
<q-btn color="green-10" label="Save">Save</q-btn>
<q-btn color="red-9" #click="open = false" label="Close">Cancel</q-btn>
</q-toolbar>
</q-modal-layout>
</q-modal>
</div>
</template>
<script>
import {
QToolbar,
QToolbarTitle,
QBtn,
QModal,
QModalLayout
} from 'quasar-framework'
export default {
name: 'app',
components: {
QToolbar,
QToolbarTitle,
QBtn,
QModal,
QModalLayout
},
data() {
return {
layoutStore: {
view: 'lHh Lpr lFf',
reveal: false,
leftScroll: true,
rightScroll: true,
leftBreakpoint: 996,
rightBreakpoint: 1200,
hideTabs: false
}
}
},
data () {
return {
addActive: false
}
}
}
</script>
<style lang="stylus">
</style>
Any help is appreciated! Cheers and thanks!!
I tried turning the Add.vue's open value to return true and the modal shows up. But when I try to use the button to turn its value to true it does not work somehow.
You're defining
addActive as empty string in Home.vue
Have you tried
addActive: false (or true if you want it to open right away)
but you need to define in props as Boolean and pass those accordingly and not in data()

Categories

Resources