Why is Vuetify reloading the image every time other card properties change? - vue.js

I am using Vuetify to create a login form for an application.
This form is similar to Google's login form which first asks for an email address then for a password on the next screen. The purpose of doing this is to look up the user's email address to see whether to perform local authentication or to redirect the user to a Single-Sign-On provider.
Here is the relevant part of the code of the login component:
<template>
<v-app id="sm-login">
<v-content>
<v-container class="fill-height" fluid>
<v-card class="mx-auto px-10 pb-9" width="450px" :loading="loading">
<v-card-title class="justify-center pt-12"><img src="../../images/logo.png"></v-card-title>
<v-card-subtitle class="text-center py-6 headline">Sign In</v-card-subtitle>
<v-card-text>
<v-form v-if="!showPassword" v-on:submit.prevent="lookupEmail">
<v-text-field v-model.trim="email" label="Email" name="email" type="email" outlined />
</v-form>
<v-form v-if="showPassword" v-on:submit.prevent="login">
<v-chip outlined class="mb-6" close #click:close="showPassword = false"><v-avatar left><v-icon>mdi-account-circle</v-icon></v-avatar> {{email}}</v-chip>
<v-text-field v-model="password" label="Password" name="password" type="password" outlined />
</v-form>
</v-card-text>
<v-card-actions>
<v-btn text>Forgot Password?</v-btn>
<v-spacer />
<v-btn color="primary" v-on:click="clickButton">Log In</v-btn>
</v-card-actions>
</v-card>
</v-container>
</v-content>
</v-app>
</template>
<script>
export default {
data() {
return {
loading: false,
email: '',
password: '',
showPassword: false,
};
},
methods: {
lookupEmail() {
this.loading = true;
// Replaced an API call with a timeout for demonstration purposes
setTimeout(() => {
this.loading = false;
this.showPassword = true;
}, 2000);
},
login() {
// DO LOGIN HERE ...
},
clickButton() {
if (this.showPassword) {
this.login();
} else {
this.lookupEmail();
}
}
}
};
</script>
The problem here is the logo in the v-card-title section. Every time the loading property is toggled on or off, the entire card jumps slightly up and then down again. I figured out that adding the logo's explicit height and width fixes the jumping, and it does, but the image still flickers every time.
Using the DevTools' Network tab, I discovered that the logo is reloaded from the server every time the loading bar appears or disappears. Needless to say, this is not the desired or expected behavior.
Why is the logo reloading every time, and what can I do about it?

This was a bug in how the <v-card> element is rendered.
Rendering the initial <v-card> creates div elements with the following classes: v-card__title, v-card__subtitle, v-card__text, and v-card__actions.
What should happen when loading is set to true is that a new div with class v-card__progress is created above the v-card__title element.
Instead, what is happening is that the title element is being modified to turn it into v-card__progress and a new div is created for the v-card__title. The new img tag in this new div is what causes the reload.
Again, when turning off loading, you would think this should just remove the v-card__progress element, but it doesn't. Instead, it removes the v-card__title element and modifies the v-card_progress to turn it into the title. Again, the new img tag here causes the reload.
I filed a bug report, which was fixed in v2.2.16.
NOTE: In general use, this is not typically noticeable in the browser. The only reason I saw it is because I had the DevTools open with "Disable Cache" checked.

It's weird, could you delete the <v-content> and try it again? just like:
<template>
<v-app id="sm-login">
<v-container class="fill-height" fluid>
...

Related

Instance of v-dialog is generated twice – Vuetify

I have a problem with <v-dialog> that appears everywhere in my Vue.js application using Vuetify.js.
Every <v-dialog> is loading twice on the page. You can see it by looking at how it appears:
If I close one dialog the other dialog stays visible and works exactly like the first. It seems like Vue is generating two instances of it.
The code of the dialog isn't the issue, I think:
<v-dialog
v-model="dialog"
persistent
hide-overlay
no-click-animation
>
<v-card>
<v-card-title>Text</v-card-title>
<v-card-text>
<p class="subtitle-1">Some more text</p>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
depressed
>
Next
<v-icon right>mdi-chevron-right</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
The v-model is declared in data():
export default {
data() {
return {
dialog: true,
};
},
}
Any help is appreciated! Thanks.

Vue/Vuetify #change doesn't provide full $event object

I'm using Vuetify in a project and creating an edit-in-place experience. Switching between the span and text field works, and it correctly sends the update to my server. One last thing though before I can call this feature complete, is that I need to determine whether the original value of the input has changed from the new value so that I'm not posting to my server when I don't need to be.
<span
v-if="editableCategory !== `category${category.id}Ref`"
class="category-header"
#click="setCategoryEditing(`category${category.id}Ref`)">
<h4>{{ category.name }}</h4>
<v-icon small>
edit
</v-icon>
</span>
<v-text-field
v-else
:ref="`category${category.id}Ref`"
:value="category.name"
color="primary"
dense
hide-details
type="text"
outlined
#blur="updateCategory(category, $event)"
#change="updateCategory(category, $event)" />
The problem is that when when I console log the $event, I get two different responses when testing the #change and the #blur. The #blur seems to give me the normal, full event object and I am then able to compare old and new values correctly. However, the #change event simply gives me a string of the new value.
Is this an issue with the Vuetify v-text-field #change event not firing correctly (and I should therefore create a Github issue with them?), or am I totally misunderstanding blur/change events (also another very real possibility)?
Blur event usually returns the event from the element attached. But the change event returns the updated value from the input element
but still you can able to read the updated value from the blur event using the below approach
Working codepen here, check the console for expected output: https://codepen.io/chansv/pen/JjjaZOJ?editors=1010
<div id="app">
<v-app id="inspire">
<v-form>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="3">
<v-text-field
label="Outlined"
placeholder="Placeholder"
outlined
#change="updateCategory($event, category)"
#blur="updateCategory($event.target.value, category)"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-form>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: {
category: 'category text',
},
methods: {
updateCategory(event, category) {
console.log(event, category);
},
}
})

CSS issues with Vuetify for responsivity using Vue-Split-Panel and select border

I'm having a variety of CSS issues with Vuetify that I'm hoping someone can help me resolve. I'm using a split panel view (vue-split-panel) with Vuetify, but Vuetify doesn't seem to consistently recognize when to trigger the full-column width, as shown below. I'm able to "trigger" the full column width (for the same split panel width) by just opening and then closing the Chrome js console.
I put this into a codesandbox so that it's reproducible. In doing so, I see a new issue that the radio buttons aren't showing.
https://codesandbox.io/s/split-view-test-7mlx1
If you're able to show me how to tweak the sandbox to make the responsivity work I'd so appreciate it!
Supposed to be a radio button:
Also, an issue that I can't reproduce in the codesandbox but I'm experiencing in my app (it's a JupyterLab extension) is shown in the bottom screenshot: the select label has the border line going through it. I tried to find if there is a CSS conflict somewhere but didn't know exactly where to look.
Furthermore I also have an issue that the select menu is offset proportional to the left menu, for some reason... why does opening the left and top menus effect the position? How can I fix it? I've tried using the "attach" property and adding an id to the element itself, or creating a parent div, but neither seems to solve it.
This is ~slightly reproducible in the sandbox by making the split panel wide and clicking the multi-select, then making it narrower and clicking again. You'll see that the menu is offset when it opens.
Solutions that don't involve iFrames would be preferred, and yes, I do have my app wrapped with <v-app>, however since it's a JupyterLab extension I only have access to the main tab space (not the left or top menus) so the v-app is wrapped around the HTML element which is the main tab area, not the full screen.
I think there might be a bug in the Vuetify code somewhere around this function: https://github.com/vuetifyjs/vuetify/blob/054555a42e2ef368df2d6e168d1eec7fc06fb12c/packages/vuetify/src/components/VSelect/VSelect.ts#L456
I have resolved all the CSS issues
Refactotred the grid layout by adding the proper components and breakdowns in UI. Added a fix to radio buttons. Added the css dependencies, material icons dependencies, fonts which vuetify uses internally
Check for the working codepen here: https://codesandbox.io/s/split-view-test-47f2h
<template>
<div id="app">
<v-app>
<Split style="height: 500px;">
<SplitArea :size="25">panel left</SplitArea>
<SplitArea :size="75">
<v-container fluid grid-list-md>
<v-layout row wrap>
<v-flex class="d-flex" xs="12" sm="12" md="6" lg="4">
<v-text-field
v-model="params.c.selected"
label="C"
hint="Penalty parameter C of the error term."
persistent-hint
return-object
type="number"
outlined
></v-text-field>
</v-flex>
<v-flex class="d-flex" sm="12" md="12" lg="6">
<v-select
v-model="params.kernel.selected"
hint="Specifies the kernel type to be used in the algorithm. It must be one of ‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’ or a callable. If none is given, ‘rbf’ will be used. If a callable is given it is used to pre-compute the kernel matrix from data matrices; that matrix should be an array of shape (n_samples, n_samples)."
:items="params.kernel.items"
label="Kernel"
persistent-hint
return-object
outlined
></v-select>
</v-flex>
<v-flex class="d-flex" sm="12" md="12" lg="6">
<v-text-field
v-model="params.degree.selected"
label="Degree"
hint="Degree of the polynomial kernel function ('poly'). Ignored by all other kernels."
persistent-hint
return-object
type="number"
outlined
></v-text-field>
</v-flex>
<v-flex class="d-flex" sm="12" md="12" lg="6">
<v-text-field
v-model="params.coef0.selected"
label="Coef0"
hint="Independent term in kernel function. It is only significant in 'poly' and 'sigmoid'."
persistent-hint
type="number"
outlined
></v-text-field>
</v-flex>
<v-flex class="d-flex" sm="12" md="12" lg="6">
<v-radio-group
v-model="params.probability.selected"
hint="Independent term in kernel function. It is only significant in 'poly' and 'sigmoid'."
persistent-hint
>
<template v-slot:label>
<div style="font-size: 12px">
Probability:
boolean, optional (default=False)
</div>
<br>
</template>
<v-radio label="True" :value="true" color="black"></v-radio>
<v-radio label="False" :value="false" color="black"></v-radio>
</v-radio-group>
</v-flex>
</v-layout>
</v-container>
</SplitArea>
</Split>
</v-app>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
params: {
c: { default: 1, selected: 1 },
kernel: {
default: "rbf",
selected: "rbf",
items: ["linear", "poly", "rbf", "sigmoid", "precomputed"]
},
degree: { default: 3, selected: 3 },
coef0: { defaul: 0.0, selected: 0.0 },
probability: { default: true, selected: true }
}
};
}
};
</script>
<style>
</style>

Reset Vuetify form validation

I have trouble resetting vuetify validation in v-dialog.
This codepen is the simplified version of what I have.
https://codepen.io/yuukive/pen/BVqpEZ
With the code above, if I do
(Open dialog --> press SAVE button --> (validation fails) --> press CLOSE button --> open dialog again),
it is already validated when I open the dialog again...
Is it possible to reset validation before a user opens it the 2nd time?
new Vue({
el: '#app',
data: () => ({
dialog: false,
emailRules: [v => !!v || 'Name is required']
}),
methods: {
onSave() {
if (!this.$refs.form.validate()) return
dialog = false
}
}
})
<div id="app">
<v-app id="inspire">
<v-layout row justify-center>
<v-dialog v-model="dialog" persistent max-width="500px">
<v-btn slot="activator" color="primary" dark>Open Dialog</v-btn>
<v-card>
<v-card-title>
<span class="headline">Email</span>
</v-card-title>
<v-form ref="form">
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field label="Email" required :rules="emailRules"></v-text-field>
</v-flex>
</v-layout>
</v-container>
<small>*indicates required field</small>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" flat #click.native="dialog = false">Close</v-btn>
<v-btn color="blue darken-1" flat #click.native="onSave">Save</v-btn>
</v-card-actions>
</v-form>
</v-card>
</v-dialog>
</v-layout>
</v-app>
</div>
Example from docs uses:
this.$refs.form.reset()
Note that while reset() clears validation, it clears input as well.
You can follow this issue to see further updates on this.
So you can perhaps watch dialog value and reset the form:
watch: {
dialog() {
this.$refs.form.reset()
}
}
resetValidation() will clear validation errors only, reset() will also clear input fields.
this.$refs.form.reset() might work on JavaScript, but compiler for TypeScript complains about typing. Even though during serve you can only see errors in the terminal without breaking the app, it'll actually break when you'll try to build the app.
Creating a new variable and assigning into it any type does the trick, below an example:
const refForm: any = this.$refs.form;
refForm.reset();

How to trigger a method at component opening in vuejs?

I have a main component that is used to display items using a loop:
<v-list-tile v-for="(item, index) in items" :key="item.title">
...
<report type="item.type"> </report>
</v-list>
The report component is used to report abuse on the system, and report type may vary depending on the item from the parent loop.
As users are very unlikely to use report on a regular basis I would like to only load v-select elements when the Dialog (modal) is opened.
Using created or mounted triggers the loading method everytime the report component is generated and not when the report component is opened.
Is there a smart way to prevent this and only have the loading method within report being triggered only when the component is opened.
=== Report.vue file ===
=== This file is loaded in the parent component
<template lang="html">
<v-dialog v-model="dialog" persistent max-width="800px" lazy>
<v-btn icon slot="activator">
<v-icon>error_outline</v-icon>
</v-btn>
<v-card>
<v-card-title>
<div class="headline"><v-icon large>error_outline</v-icon> Reporting</div>
</v-card-title>
<v-card-text>You are about to report the following {{ reportType }}: "<i>{{ reportContent.title }}</i>"
<v-container v-if="this.$store.getters['report/getLoadedState']" grid-list-md >
<v-layout wrap>
<v-flex xs12 sm12>
<v-select
label="Select a reason"
required
cache-items
:items="items"
item-text="title"
item-value="id"
></v-select>
</v-flex>
<v-flex xs12 sm12>
<v-text-field
label="Please provide additional information here"
multi-line></v-text-field>
</v-flex>
</v-layout>
</v-container>
<v-container v-else grid-list-md>
<v-layout>
<v-flex xs12 sm12>
Loading
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="green darken-1" flat="flat" #click.native="cancel">Cancel</v-btn>
<v-btn color="green darken-1" flat="flat" #click.native="report">Report</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'report',
data () {
return {
dialog: false,
items: this.$store.getters['report/getItems']
}
},
props: ['reportType', 'reportContent'],
methods: {
cancel () {
this.dialog = false
},
report () {
this.dialog = false
},
loadReasons (type) {
if (!this.$store.getters['report/getLoadedState']) {
this.$store.dispatch('report/setItems', type)
}
}
}
}
</script>
<style lang="css" scoped>
</style>
PS 1: I'm not using JQuery and do not intend to use it
PS 2: Calling the method outside of the report component is not an option as I want to maximize reusability of this compenent and only pass arguments to it using props
How I have done this in the past is to use a "dynamic component." Vue uses a special syntax for dynamic components <component v-bind:is="currentView"></component> see: Vue dynamic components
You can set the "currentView" property to null and then on a button click another event set the currentView to report. Doing it this way will allow you to use the mounted method as the component is not rendered or created until it is dynamically called.
<component :is="myComponent"></component>
<v-btn #click="loadComponent">Show Report</v-btn>
then...
data:{
myComponent: null;
},
methods: {
loadComponent: function(){
this.myComponent = 'report';
}
}
P.S. You can of course use this method to render any other components in the same place. i.e. you can set the 'currentView' to the name of any available component and it will be instantly rendered in its place.
I ended up using a watch on the boolean managing the dialog...
Make a function which loads the v-select. Don't initialize it just create it. Now whenever user clicks the report model you can initialize multiple functions using v-on:click or #click.
For example :
<div v-on:click="return function() { fn1('foo');fn2('bar'); }()"> </div>
This solution can also be found on :
Stackoverflow Link