How show error in Quasar Stepper when required field is empty - javascript

I work with Quasar framwork. I create a stepper and in each step I've several field. Some are required
like this:
<q-stepper dense v-model="step" ref="stepper" color="primary" animated header-nav>\
<q-step dense :name="1" title="Infos générales" icon="settings" :done="step > 1" :error="step < 3">
<q-card-section class="q-pt-none">
<div class="q-col-gutter-x-md">
<q-input class="full-width" dense v-model="infos.motif" label="Raison *" hint="Détaillez la raison. * Champs requis" :rules="Required"></q-input>
</div>
</q-card-section >
</q-step>
<q-step dense :name="2" title="Mise en copie" icon="assignment" :done="step > 2" >
<q-card-section class="q-pt-none" >
<div class="row items-start content-start" >
<div class="text-subtitle2">A la demande</div>
<selectusers v-bind:users="users" v-bind:model.sync="infos.copiedemande"></selectusers>
</div >\
</q-card-section >
</q-step>
<template v-slot:navigation>
<q-stepper-navigation >
<q-btn v-if="step > 1" flat color="primary" #click="$refs.stepper.previous()" label="Retour" />
<q-btn #click="$refs.stepper.next()" color="primary" :label="step === 2 ? \'Fini\' : \'Continuer\'" class="float-right"/>
</q-stepper-navigation >\
</template >\
</q-stepper>
Required is a computed : Required() { return [(v) => !!v || 'Saisissez quelque chose :-)'] },
I want when i change step my first step display error if I don't fill my infos.motif field
I don't find how link :error from step to rules from field
Thanks for your guidance
UPDATE1
I Add a form with ref in my 1st page.
<q-stepper dense v-model="step" ref="stepper" color="primary" animated header-nav>
<q-step dense :name="1" ref="step1" title="Infos générales" icon="settings" :done="step > 1" :error="checkform1()">
<q-form ref="myForm1">
Where checkform1 is a method
checkform1() { return this.$refs.stepper ? this.$refs.stepper.$refs.step1.$refs.myForm1.validate() : true; }
But I can't access to my form. When I has this.$refs.stepper, the $ref is empty...
UPDATE2 I create a sample on codepen here

I make a big step... The idea is to pass by a data and on before-transtion call my checkform function
<div id="q-app">
<q-stepper
v-model="step"
header-nav
ref="stepper"
color="primary"
animated
#before-transition="checkform1"
>
<q-step
:name="1"
title="Select campaign settings"
icon="settings"
:done="done1"
:error="stateform1"
> <q-form ref="myForm" class="q-gutter-md" >
<q-input
v-model="firstName"
:rules="[val => !!val || 'First name is required']"
/>
</q-form>
<q-stepper-navigation>
<q-btn #click="() => { done1 = true; step = 2 }" color="primary" label="Continue"></q-btn>
</q-stepper-navigation>
</q-step>
<q-step
:name="2"
title="Create an ad group"
caption="Optional"
icon="create_new_folder"
:done="done2"
>
An ad group contains one or more ads which target a shared set of keywords.
<q-stepper-navigation>
<q-btn #click="() => { done2 = true; step = 3 }" color="primary" label="Continue"></q-btn>
<q-btn flat #click="step = 1" color="primary" label="Back" class="q-ml-sm"></q-btn>
</q-stepper-navigation>
</q-step>
<q-step
:name="3"
title="Create an ad"
icon="add_comment"
:done="done3"
>
text3
<q-stepper-navigation>
<q-btn color="primary" #click="done3 = true" label="Finish"></q-btn>
<q-btn flat #click="step = 2" color="primary" label="Back" class="q-ml-sm"></q-btn>
</q-stepper-navigation>
</q-step>
</q-stepper>
</div>
</div>
and js
new Vue({
el: '#q-app',
data () {
return {
step: 1,
done1: false,
done2: false,
done3: false,
firstName: '',
stateform1:false
}
},
methods: {
reset () {
this.done1 = false
this.done2 = false
this.done3 = false
this.step = 1
this.stateform1=false
this.firstName= ''
},
checkform1() {
if (this.$refs.myForm) {
this.$refs.myForm.validate().then(success => {
if (success) {
this.stateform1= false;
}
else {
this.stateform1= true;
}
});
}
} //this.step<3;
}
})
like here
But stay another problem. When I'm on specific step, the other form don't exist... and if I pass step1 tp step 3... i can't check step2
like u can see enter link description here
Finally I found another way. I keep my :error="stateform1" but I check in addition in server side and if I've a missing field I return the name of my step and change the variable when I was a error in return

Related

q-btn click event not working on q-checkbox

There is a q-checkbox inside the q-btn. I also added a div because I want to add some style to the text.
<q-btn
flat
color="blue"
class="full-width no-padding"
#click="tog(item)"
>
<q-checkbox
class="q-mr-sm"
v-model="item.toggle"
/>
<div class="text-caption text-grey" style="">{{ item.label }}</div>
<q-space />
</q-btn>
When I click on the text, the tog(item) function fires and works well, but when I click on the checkbox itself nothing happens. Is there any way to fire the tog() function when the checkbox is clicked?
Please refer this code.
<q-btn color="white" text-color="black" label="Standard" #click.stop="tog()">
<q-checkbox
class="q-mr-sm"
v-model="item.toggle"
/>
<div class="text-caption text-grey" style="">{{ item.label }}</div>
<q-space />
</q-btn>
const { ref } = Vue
const app = Vue.createApp({
setup () {
return {
item: ref({toggle: false, label: 'aaa'})
}
},
methods:{
tog(){
this.item.toggle = !this.item.toggle
}
}
})
app.use(Quasar, { config: {} })
app.mount('#q-app')
Codepen - https://codepen.io/Pratik__007/pen/zYjEENr

Submit not working when there is a q-select

I have added a q-select And I gate my form a submit event. Submit event not working when there is a q-select.But without the q-select it is working.
<q-form #submit.prevent="addNewRole" class="q-gutter-md">
<q-input
v-model="newRoleForm.name"
type="text"
autofocus
label="Role Name"
color="info"
required
/>
<q-select
v-model="newRoleForm.accessLevel"
:options="accessTypes"
label="Access Level"
color="info"
/>
<q-btn
class="q-mt-lg"
color="primary"
icon="add_moderator"
label="Add Role"
/>
</q-form>
<script setup>
const addNewRole = () => {
alert("works");
};
</script>
You should remove the .prevent modifier, set the type of the button to submit, add a rules prop to the q-input and define the binded data:
<template>
<q-form class="q-gutter-md" #submit="addNewRole">
<q-input
v-model="newRoleForm.name"
type="text"
autofocus
label="Role Name"
color="info"
:rules="[ruleRequired]"
/>
<q-select
v-model="newRoleForm.accessLevel"
:options="accessTypes"
label="Access Level"
color="info"
/>
<q-btn
type="submit"
class="q-mt-lg"
color="primary"
icon="add_moderator"
label="Add Role"
/>
</q-form>
</template>
<script>
export default
{
data()
{
return {
newRoleForm:
{
name: '',
accessLevel: null,
},
};
},
computed:
{
accessTypes()
{
return [
{
value: 'full',
label: 'Full access',
},
{
value: 'restricted',
label: 'Restricted access',
},
];
},
},
methods:
{
ruleRequired(val)
{
return typeof val === 'number' ? true : (Array.isArray(val) ? val.length > 0 : !!val) || 'Required field';
}
}
}
</script>
Add type submit for the button, also remove prevent from #submit.prevent
<q-btn
type="submit"
class="q-mt-lg"
color="primary"
icon="add_moderator"
label="Add Role"
/>

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,
})

How to access data from a loop in a vuetify carousel?

I use vuetify to make a kind of carousel to display recipes that are stored in the database.
But I would like when I click on a recipe the carousel opens below a space with all the elements of the recipe in question (the one we clicked on) So I found a component on vuetify that corresponds exactly to what I'm looking for: Here is Vuetify Carousel
But in my v-slide-item I use a loop that retrieves the recipe data but suddenly from the v-expand-transition I no longer have access to this loop how can I display the recipe data suddenly?
Here is the code :
<template>
<v-sheet
class="mx-auto"
elevation="8"
max-width="100%"
style="box-shadow: none !important;"
>
<v-slide-group
v-model="model"
class="pa-4 slider"
show-arrows
>
<v-slide-item
v-for="n in Object.keys(recipes)"
:key="recipes[n].id"
v-slot="{ active, toggle }"
>
<v-card
:color=" 'grey lighten-1'"
class="ma-4 card-recipe"
height="200"
width="200"
style="border-radius: 10px;"
v-bind:style="recipes[n].recipe[0].first_recipes_image != null ? { backgroundImage: 'url(' + recipes[n].recipe[0].first_recipes_image + ')' } : { backgroundImage: 'url(https://cdn.vuetifyjs.com/images/cards/sunshine.jpg)' }"
#click="toggle"
>
<p class="card-text" ><span class="black--text">{{ recipes[n].recipe[0].name }}</span></p>
<v-row
class="fill-height"
align="center"
justify="center"
>
<v-scale-transition>
<v-icon
v-if="active"
color="white"
size="48"
v-text="'mdi-close-circle-outline'"
></v-icon>
</v-scale-transition>
</v-row>
</v-card>
</v-slide-item>
</v-slide-group>
<v-expand-transition>
<v-sheet
v-if="model != null"
height="200"
tile
style="background-color: #FFF8F0 !important;"
>
<v-row
class="fill-height"
align="center"
justify="center"
>
<h3 class="text-h6">
Selected {{ model }}
</h3>
</v-row>
</v-sheet>
</v-expand-transition>
</v-sheet>
</template>
<script>
import { mapGetters } from "vuex";
export default {
props: {
},
data: () => ({
model: null,
recipes: [],
openedCards: [],
}),
computed: {
console: () => console,
...mapGetters({
plantActive: 'permatheque/getPlant',
}),
},
methods: {
async getPlantRecipes() {
this.$axios
.$get("/lnk/plant/recipes?plant_id=" + this.plantActive.id + "")
.then((response) => {
this.recipes = response;
console.log(this.recipes);
})
.catch((error) => {
console.log(error);
});
},
},
mounted() {
this.getPlantRecipes()
}
}
</script>
Hope I was clear enough, thanks!
You can use the v-model of the v-slide-group which is basically the index from recipes array of the selected item in the carousel.
This way you know which recipe is selected, so you can go grab the recipe info from the array or make another api call to get that info.
Example
Check this codesandbox I made: https://codesandbox.io/s/stack-71474788-recipes-carousel-6vm97o?file=/src/components/Example.vue
If you already have the recipe info within your recipes array, all you need to do is use the v-model variable of the v-slide-group which I renamed to recipeIndex to access that data directly from your array.
<v-expand-transition>
<v-sheet v-if="recipeIndex != null" tile style="background-color: #FFF3E0 !important;">
<v-container fluid class="pa-12">
<v-row>
<v-col cols="12" sm="6">
<span class="text-h6">{{ `${recipes[recipeIndex].name}'s Recipe` }}</span> <br>
<span class="text-subtitle-1">{{ recipes[recipeIndex].description }}</span>
<p class="text-justify mt-4">
{{ recipes[recipeIndex].steps }}
</p>
</v-col>
<v-col cols="12" sm="6" class="d-flex align-center justify-center">
<div class="thumbnail">
<img :src="recipes[recipeIndex].image" alt="Beach Scene" style="width: 100%;" />
</div>
</v-col>
</v-row>
</v-container>
</v-sheet>
</v-expand-transition>
If you need to get the info from a secondary api call. You can set up a watcher on the recipeIndex variable to obtain the recipe info everytime it changes.

mdbreact error - Element type is invalid: expected a string (for built-in components)

I am trying to implement the react-multistep registration using mdb react but I am getting the error:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
I tried in different ways to resolve this issue using Github and Stack Overflow but I can't find the problem in my code.
Here is my code:
import React,{Component} from 'react'
import {Container,Step,Row,Col,Stepper,Input,Button} from 'mdbreact'
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
formActivePanel2: 1,
formActivePanel2Changed: false,
}
}
swapFormActive = (a) => (param) => (e) => {
this.setState({
['formActivePanel' + a]: param,
['formActivePanel' + a + 'Changed']: true
});
}
handleNextPrevClick = (a) => (param) => (e) => {
this.setState({
['formActivePanel' + a] : param,
['formActivePanel' + a + 'Changed']: true
});
}
handleSubmission = () => {
alert('Form submitted!');
}
calculateAutofocus = (a) => {
if (this.state['formActivePanel'+a+'Changed']) {
return true
}
}
render() {
return(
<Container>
<Row className="pt-5 justify-content-center">
<Col md="2" className="pl-5 pl-md-0 pb-5">
<Stepper icon vertical>
<Step icon="folder-open-o" stepName="Basic Information" onClick={this.swapFormActive(2)(1)} vertical></Step>
<Step icon="pencil" stepName="Personal Data" onClick={this.swapFormActive(2)(2)} vertical></Step>
<Step icon="photo" stepName="Terms and Conditions" onClick={this.swapFormActive(2)(3)} vertical></Step>
<Step icon="check" stepName="Finish" onClick={this.swapFormActive(2)(4)} vertical></Step>
</Stepper>
</Col>
<Col md="7">
{ this.state.formActivePanel2 == 1 &&
(<Col md="12">
<h3 className="font-weight-bold pl-0 my-4">
<strong>Basic Information</strong></h3>
<Input label="Email" className="mt-4" autoFocus={this.calculateAutofocus(2)}/>
<Input label="Username" className="mt-4"/>
<Input label="Password" className="mt-4"/>
<Input label="Repeat Password" className="mt-4"/>
<Button color="mdb-color" rounded className="float-right" onClick={this.handleNextPrevClick(2)(2)}>next</Button>
</Col>)}
{ this.state.formActivePanel2 == 2 &&
(<Col md="12">
<h3 className="font-weight-bold pl-0 my-4"><strong>Personal Data</strong></h3>
<Input label="First Name" className="mt-3" autoFocus={this.calculateAutofocus(2)}/>
<Input label="Second Name" className="mt-3"/>
<Input label="Surname" className="mt-3"/>
<Input label="Address" type="textarea" rows="2"/>
<Button color="mdb-color" rounded className="float-left" onClick={this.handleNextPrevClick(2)(1)}>previous</Button>
<Button color="mdb-color" rounded className="float-right" onClick={this.handleNextPrevClick(2)(3)}>next</Button>
</Col>)}
{ this.state.formActivePanel2 == 3 &&
(<Col md="12">
<h3 className="font-weight-bold pl-0 my-4"><strong>Terms and conditions</strong></h3>
<Input label="I agreee to the terms and conditions" type="checkbox" id="checkbox3" autoFocus={this.calculateAutofocus(2)} />
<Input label="I want to receive newsletter" type="checkbox" id="checkbox4" />
<Button color="mdb-color" rounded className="float-left" onClick={this.handleNextPrevClick(2)(2)}>previous</Button>
<Button color="mdb-color" rounded className="float-right" onClick={this.handleNextPrevClick(2)(4)}>next</Button>
</Col>)}
{ this.state.formActivePanel2 == 4 &&
(<Col md="12">
<h3 className="font-weight-bold pl-0 my-4"><strong>Finish</strong></h3>
<h2 className="text-center font-weight-bold my-4">Registration completed!</h2>
<Button color="mdb-color" rounded className="float-left" onClick={this.handleNextPrevClick(2)(3)}>previous</Button>
<Button color="success" rounded className="float-right" onClick={this.handleSubmission}>submit</Button>
</Col>)}
</Col>
</Row>
</Container>
)
}
}
export default Main
You are exporting the component Main as default and in the component where you are using this Main component, you are importing it as import {Main} from path/Main.js.So, just change it to import Main from path/Main.js.
Update:
swapFormActive = (a,param,e) => {
this.setState({
['formActivePanel' + a]: param,
['formActivePanel' + a + 'Changed']: true
});
}
handleNextPrevClick = (a,param,e) => {
this.setState({
['formActivePanel' + a] : param,
['formActivePanel' + a + 'Changed']: true
});
}
...
<Step icon="check" stepName="Finish" onClick={(e)=>this.swapFormActive(2,4,e)} vertical></Step>
<Button color="mdb-color" rounded className="float-right" onClick={(e)=>this.handleNextPrevClick(2,2,e)}>next</Button>

Categories

Resources