I started a fresh vue3 project with vuetify3 via vue cli.
$ vue create playground-vuelidate
$ cd playground-vuelidate
$ code .
$ vue add vuetify
$ npm run serve
FormInput.vue
<template>
<v-form>
<v-container>
<v-row no-gutters>
<v-col cols="12" sm="12">
<v-combobox :items="select1" label="Select1" chips></v-combobox>
</v-col>
<v-col cols="12" sm="12">
<v-text-field
outline
v-model="input1"
:rules="rules"
label="Input1"
></v-text-field>
</v-col>
<v-col cols="12" sm="12">
<v-text-field
outline
v-model="input2"
:rules="rules"
label="Input2"
></v-text-field>
</v-col>
<v-col cols="12" sm="12">
<v-text-field outline v-model="input3" label="Input3"></v-text-field>
<v-textarea
v-model="v$.textarea1.$model"
:class="status(v$.textarea1)"
label="textarea1"
></v-textarea>
<pre>{{ $v }}</pre>
</v-col>
</v-row>
</v-container>
</v-form>
</template>
<script>
import { maxLength, required } from "#vuelidate/validators";
import { useVuelidate } from "#vuelidate/core";
export default {
name: "UsersPage",
data: () => ({
select1: ["Foo", "Bar", "Fizz", "Buzz"],
input1: "",
input2: "222",
input3: "333",
textarea1: "12345678910",
rules: [
(value) => !!value || "Required.",
(value) => (value || "").length <= 20 || "Max 20 characters",
],
}),
setup: () => ({ v$: useVuelidate() }),
validations() {
return {
textarea1: {
required,
minlength: maxLength(20),
},
};
},
methods: {
status(validation) {
return {
error: validation.$error,
dirty: validation.$dirty,
};
},
},
};
</script>
<style>
input {
border: 1px solid silver;
border-radius: 4px;
background: white;
padding: 5px 10px;
}
.dirty {
border-color: #5a5;
background: #efe;
}
.dirty:focus {
outline-color: #8e8;
}
.error {
border-color: red;
background: #fdd;
}
.error:focus {
outline-color: #f99;
}
</style>
App.vue
<template>
<v-card class="mx-auto" max-width="1000">
<FormInput />
<v-card-actions>
<v-btn text color="gray accent-4"> Cancel </v-btn>
<v-btn color="primary"> Submit </v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import FormInput from "./components/FormInput.vue";
export default {
name: "App",
components: {
FormInput,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
It yields this result
It supposes to look like this.
Official Doc
Notes
I have a select, inputs, and textarea all 3 of them the outline CSS doesn't seem to work or applied. Is it a known Vuetify bug ?
Should be
<v-text-field
outlined
></v-text-field>
not
<v-text-field
outline
></v-text-field>
This is based from vuetify for vue3
https://next.vuetifyjs.com/en/components/text-fields/
Related
I am trying to test if the dialog created with vuetify will be active or not after I emit a function called closeNoteForm. However, when I try to test the dialog content being hidden or not, I get an error. It seems the problem is within the noteForm wrapper that I created. But it just points to the css and doesn't make much sense to me.
In my NoteForm component
<template>
<v-card class="note-form-container">
<v-text-field
v-model="title"
placeholder="Note Title"
hide-details
class="font-weight-bold text-h5 pa-2"
flat
solo
color="yellow"
>
</v-text-field>
<vue-tags-input
v-model="tag"
:tags="tags"
#tags-changed="(newTags) => (tags = newTags)"
/>
<div class="mt-2">
<input
type="file"
id="uploadImg"
style="display: none"
multiple
accept="image/*"
v-on:change="handleFileUploads"
/>
<label class="text-button pa-2 upload__label ml-3 mt-2" for="uploadImg"
>Upload Image</label
>
</div>
<v-container>
<v-row justify="space-around"
><div v-for="(image, index) in allImages" :key="index">
<div style="position: relative">
<v-img
:src="image"
height="70px"
width="70px"
contain
class="mx-2 uploaded__image"
#click="openImage(image)"
></v-img>
<v-btn
icon
color="pink"
class="close__button"
#click="handleDeleteButton(image)"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
</div>
<v-spacer></v-spacer>
</v-row>
</v-container>
<v-textarea
v-model="text"
clearable
clear-icon="mdi-close-circle"
no-resize
hide-details
solo
flat
></v-textarea>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text #click="closeForm()" class="close__btn"> Close </v-btn>
<v-btn color="secondary darken-2" text #click="saveNote"> Save </v-btn>
</v-card-actions>
<v-dialog v-model="dialog" width="500" dark>
<v-img :src="selectedImage" #click="dialog = false"></v-img>
</v-dialog>
<v-dialog
v-model="imageDeletionDialog"
width="500"
class="image_delete_dialog"
>
<v-img :src="selectedImage"></v-img>
<v-btn color="red darken-1" text #click="deleteImage">
Delete Image
</v-btn>
<v-btn text #click="imageDeletionDialog = false"> Close </v-btn>
</v-dialog>
</v-card>
</template>
<script>
import { EventBus } from "../event-bus";
import VueTagsInput from "#johmun/vue-tags-input";
import { v4 as uuidv4 } from "uuid";
import dbService from "../services/db_service";
export default {
name: "NoteForm",
components: { VueTagsInput },
data: () => {
return {
text: "",
title: "",
tag: "",
tags: [],
currentNoteID: null,
allImages: [],
dialog: false,
selectedImage: "",
imageDeletionDialog: false,
};
},
mounted() {
EventBus.$on("editNote", (noteToEdit) => {
this.fillNoteForm(noteToEdit);
});
},
methods: {
openImage(image) {
this.selectedImage = image;
this.dialog = true;
},
handleDeleteButton(image) {
this.imageDeletionDialog = true;
this.selectedImage = image;
},
deleteImage() {
this.allImages = this.allImages.filter(
(img) => img !== this.selectedImage
);
this.imageDeletionDialog = false;
this.selectedImage = "";
},
handleFileUploads(e) {
const images = e.target.files;
let imageArray = [];
for (let i = 0; i < images.length; i++) {
const reader = new FileReader();
const image = images[i];
reader.onload = () => {
imageArray.push(reader.result);
};
reader.readAsDataURL(image);
}
this.allImages = imageArray;
e.target.value = "";
},
saveNote() {
if (this.title.trim() === "") {
alert("Please enter note title!");
return;
}
if (this.text === null) this.text = "";
if (this.currentNoteID === null) {
this.createNewNote();
} else {
this.updateNote();
}
this.resetForm();
},
createNewNote() {
const tagList = this.tags.map((tag) => {
return tag.text;
});
const uuid = uuidv4();
const newNote = {
title: this.title,
tags: this.tags,
text: this.text,
uuid,
date: new Date().toLocaleString(),
tagList,
allImages: this.allImages,
};
dbService.addNote(newNote);
EventBus.$emit("addNewNote", newNote);
EventBus.$emit("closeNoteForm");
},
updateNote() {
const tagList = this.tags.map((tag) => {
return tag.text;
});
let updatedNote = {
tags: this.tags,
uuid: this.currentNoteID,
text: this.text,
title: this.title,
tagList: tagList,
allImages: this.allImages,
};
dbService.updateNote(updatedNote);
EventBus.$emit("updateNote", updatedNote);
},
resetForm() {
this.tags = [];
this.text = "";
this.title = "";
this.currentNoteID = null;
this.allImages = [];
this.selectedImage = "";
},
closeForm() {
if (this.currentNoteID !== null) {
EventBus.$emit("openNoteView", this.currentNoteID);
}
this.resetForm();
**EventBus.$emit("closeNoteForm");**
},
fillNoteForm(noteToEdit) {
this.text = noteToEdit.text;
this.title = noteToEdit.title;
this.tags = noteToEdit.tags;
this.currentNoteID = noteToEdit.uuid;
this.allImages = noteToEdit.allImages;
},
},
beforeDestroy() {
EventBus.$off("fillNoteForm", this.fillNoteForm);
},
};
</script>
<style lang="scss" >
.uploaded__image {
position: relative;
cursor: pointer;
}
.close__button {
position: absolute;
top: 0;
right: 0;
}
.upload__label {
background-color: gray;
cursor: pointer;
&:hover {
background-color: lightgrey;
}
}
.note-form-container {
scrollbar-width: none;
}
.v-input__control,
.v-input__slot,
.v-text-field__slot {
height: 100% !important;
}
.v-textarea {
height: 350px !important;
}
.vue-tags-input {
max-width: 100% !important;
border: none;
background: transparent !important;
}
.v-dialog {
background-color: rgb(230, 230, 230);
}
.vue-tags-input .ti-tag {
position: relative;
}
.vue-tags-input .ti-input {
padding: 4px 10px;
transition: border-bottom 200ms ease;
border: none;
height: 50px;
overflow: auto;
}
.note-form-container.theme--dark {
.vue-tags-input .ti-tag {
position: relative;
background: white;
color: black !important;
}
.vue-tags-input .ti-new-tag-input {
color: #07c9d2;
}
}
.note-form-container.theme--light {
.vue-tags-input .ti-tag {
position: relative;
background: black;
color: white !important;
}
.vue-tags-input .ti-new-tag-input {
color: #085e62;
}
}
</style>
My Note component
<template>
<v-dialog width="500px" v-model="dialog">
<template v-slot:activator="{ on, attrs }">
<v-card
v-bind="attrs"
v-on="on"
height="200px"
color="primary"
class="note_card"
>
<v-card-title class="text-header font-weight-bold white--text"
>{{ note.title }}
</v-card-title>
<v-card-subtitle
v-if="note.text.length < 150"
class="text-caption white--text note__subtitle"
>
{{ note.text }}
</v-card-subtitle>
<v-card-subtitle v-else class="text-caption note__subtitle">
{{ note.text.substring(0, 150) + ".." }}
</v-card-subtitle>
</v-card>
</template>
<template>
<v-card class="read_only_note">
<v-card-subtitle class="text-h4 black--text font-weight-bold pa-5"
>{{ note.title }}
</v-card-subtitle>
<v-card-subtitle>
<span
v-for="(note, index) in this.note.tagList"
:key="index"
class="tag_span"
>
{{ note }}
</span>
</v-card-subtitle>
<v-container>
<v-row justify="space-around" class="note__images"
><div v-for="(image, index) in note.allImages" :key="index">
<v-img
:src="image"
height="70px"
width="70px"
contain
class="mx-2"
#click="openImage(image)"
></v-img>
</div>
<v-spacer></v-spacer>
</v-row>
</v-container>
<v-card-subtitle class="text__container">
<p v-html="convertedText" #click="detectYoutubeClick"></p>
</v-card-subtitle>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="red darken-1" text #click="deleteDialog = true">
Delete
</v-btn>
<v-btn color="secondary darken-2" text #click="closeNoteView()">
Close
</v-btn>
<v-btn color="secondary darken-2" text #click="editNote">
Edit
</v-btn>
</v-card-actions>
</v-card>
</template>
<v-dialog v-model="imageDialog" width="500">
<v-img :src="selectedImage" #click="imageDialog = false"></v-img>
</v-dialog>
<v-dialog v-model="deleteDialog" width="500">
<h4 style="text-align: center" class="pa-5">
Are you sure you want to delete {{ note.title }}?
</h4>
<v-btn color="red darken-1" text #click="deleteNote()"> Delete </v-btn>
<v-btn color="blue darken-1" text #click="deleteDialog = false">
Close
</v-btn>
</v-dialog>
<v-dialog v-model="youtubeDialog" width="500">
<iframe
id="ytplayer"
type="text/html"
width="500"
height="400"
:src="youtubeSrc"
frameborder="0"
></iframe>
</v-dialog>
</v-dialog>
</template>
<script>
import { EventBus } from "../event-bus";
export default {
props: {
note: Object,
},
data: () => {
return {
dialog: false,
imageDialog: false,
deleteDialog: false,
selectedImage: "",
youtubeDialog: false,
youtubeSrc: "",
};
},
mounted() {
EventBus.$on("openNoteView", (noteID) => {
if (this.note.uuid === noteID) {
return this.openNoteView();
}
});
},
methods: {
openImage(image) {
this.selectedImage = image;
this.imageDialog = true;
},
deleteNote() {
EventBus.$emit("deleteNote", this.note.uuid);
},
editNote() {
EventBus.$emit("openNoteForm");
this.closeNoteView();
// To load data after note form is mounted
setTimeout(() => {
EventBus.$emit("editNote", this.note);
}, 200);
},
openNoteView() {
this.dialog = true;
},
closeNoteView() {
this.dialog = false;
},
detectYoutubeClick(e) {
if (e.target.innerText.includes("youtube")) {
const url = e.target.innerText.replace("watch?v=", "embed/");
this.youtubeSrc = !url.includes("http") ? "https://" + url : url;
this.youtubeDialog = true;
}
},
},
computed: {
convertedText: function () {
const urlRegex = /(((https?:\/\/)|(www\.))[^\s]+)/g;
return this.note.text.replace(urlRegex, function (url, b, c) {
const url2 = c == "www." ? "http://" + url : url;
if (url2.includes("youtube")) {
return `<span class="youtube_url">${url}</span>`;
} else {
return '' + url + "";
}
});
},
},
watch: {
youtubeDialog: function (newVal) {
if (newVal === false) this.youtubeSrc = "";
},
},
beforeDestroy() {
EventBus.$off("openNoteView", this.openNoteView);
},
};
</script>
<style lang="scss" >
.text__container {
height: 50vh;
p {
width: 100%;
}
}
.note__images {
margin-left: 5px !important;
}
.note_card {
border: 1px solid black !important;
}
.theme--dark.v-card .v-card__title {
color: black !important;
}
.theme--dark.v-card .v-card__subtitle.note__subtitle {
color: black !important;
}
.theme--light.v-card .v-card__subtitle.note__subtitle {
color: white !important;
}
.read_only_note.theme--dark {
.v-card__subtitle {
color: white !important;
display: flex;
flex-wrap: wrap;
}
.tag_span {
background-color: white;
color: black !important;
padding: 2px;
border-radius: 3px;
margin: 5px;
}
}
.read_only_note.theme--light {
.v-card__subtitle {
display: flex;
flex-wrap: wrap;
}
.tag_span {
color: white !important;
background-color: #212121;
padding: 2px;
border-radius: 3px;
margin: 5px;
}
}
.youtube_url {
text-decoration: underline;
&:hover {
cursor: pointer;
}
}
</style>
My spec file
import Vue from 'vue';
Vue.use(Vuetify);
import Vuetify from 'vuetify';
import NoteForm from '#/components/NoteForm';
import Note from '#/components/Note';
import { createLocalVue, mount } from '#vue/test-utils';
describe('NoteForm.vue', () => {
const localVue = createLocalVue();
localVue.use(Vuetify);
document.body.setAttribute('data-app', true);
let vuetify;
beforeEach(() => {
vuetify = new Vuetify();
});
it('should emit an event when the action v-btn is clicked', async () => {
const formWrapper = mount(NoteForm, {
localVue,
vuetify,
});
const noteWrapper = mount(Note, {
localVue,
vuetify,
});
formWrapper.vm.$emit('closeNoteForm');
await formWrapper.vm.$nextTick(); // Wait until $emits have been handled
expect(formWrapper.emitted().closeNoteForm).toBeTruthy();
expect(noteWrapper.find('.read_only_note').isVisible()).toBe(true);
});
});
However im getting the error
[Vue warn]: Error in render: "TypeError: Cannot read property 'title' of undefined"
found in
---> <Anonymous>
<Root>
console.error node_modules/vue/dist/vue.runtime.common.dev.js:1884
TypeError: Cannot read property 'title' of undefined
at Proxy.render (/Users/ozansozuoz/Downloads/vue-notebook/src/components/Note.vue:199:832)
at VueComponent.Vue._render (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:3538:22)
at VueComponent.updateComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4054:21)
at Watcher.get (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at init (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:3112:13)
at createComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:5958:9)
at createElm (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:5905:9)
at VueComponent.patch [as __patch__] (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:6455:7)
at VueComponent.Vue._update (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:3933:19)
at VueComponent.updateComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4054:10)
at Watcher.get (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at mount (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/#vue/test-utils/dist/vue-test-utils.js:13977:21)
at Object.<anonymous> (/Users/ozansozuoz/Downloads/vue-notebook/tests/unit/example.spec.js:24:25)
at Object.asyncJestTest (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
at /Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/queueRunner.js:43:12
at new Promise (<anonymous>)
at mapper (/Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
at /Users/ozansozuoz/Downloads/vue-notebook/node_modules/jest-jasmine2/build/queueRunner.js:73:41
at processTicksAndRejections (internal/process/task_queues.js:93:5)
FAIL tests/unit/example.spec.js
NoteForm.vue
✕ should emit an event when the action v-btn is clicked (501ms)
● NoteForm.vue › should emit an event when the action v-btn is clicked
TypeError: Cannot read property 'title' of undefined
197 | }
198 | .read_only_note.theme--dark {
> 199 | .v-card__subtitle {
| ^
200 | color: white !important;
201 | display: flex;
202 | flex-wrap: wrap;
at Proxy.render (src/components/Note.vue:199:832)
at VueComponent.Vue._render (node_modules/vue/dist/vue.runtime.common.dev.js:3538:22)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4054:21)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at init (node_modules/vue/dist/vue.runtime.common.dev.js:3112:13)
at createComponent (node_modules/vue/dist/vue.runtime.common.dev.js:5958:9)
at createElm (node_modules/vue/dist/vue.runtime.common.dev.js:5905:9)
at VueComponent.patch [as __patch__] (node_modules/vue/dist/vue.runtime.common.dev.js:6455:7)
at VueComponent.Vue._update (node_modules/vue/dist/vue.runtime.common.dev.js:3933:19)
at VueComponent.updateComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4054:10)
at Watcher.get (node_modules/vue/dist/vue.runtime.common.dev.js:4465:25)
at new Watcher (node_modules/vue/dist/vue.runtime.common.dev.js:4454:12)
at mountComponent (node_modules/vue/dist/vue.runtime.common.dev.js:4061:3)
at VueComponent.Object.<anonymous>.Vue.$mount (node_modules/vue/dist/vue.runtime.common.dev.js:8392:10)
at mount (node_modules/#vue/test-utils/dist/vue-test-utils.js:13977:21)
at Object.<anonymous> (tests/unit/example.spec.js:24:25)
I don't understand why its pointing to my scss and saying undefined?
You don't add props to your wrapper in tests.
Add this code to your test.
const noteWrapper = mount(Note, {
localVue,
vuetify,
propsData: {
note: {
title: '',
}
});
I'm trying to create a floating action button by using vuetify -vspeed dial. I created a logic to style my button whenever it's clicked and it's working perfect, it collapses and expands whenever i click on it. However, if i try to click the focus area of the action buttons, it breaks it and close the buttons. How can i prevent that? When I click on button, it's fine - I use click.stop to make it persistent but if i click to the area right next to button, it closes the buttons which breaks my logic for styling. Here's my code
Test.Vue
<template>
<v-card :class="{create: backgroundColor }">
<v-speed-dial
:bottom="true"
:right="true"
:direction="direction"
:transition="transition"
fixed
>
<template v-slot:activator>
<v-btn
:class="{is_active:isActive}"
color="#C6002B"
fab
dark
#click=toggleButton
x-large
>
<v-icon>{{isActive? 'mdi-close' : 'mdi-account-circle'}}</v-icon><span>{{isActive ? "EXPANDED" : ''}}</span>
</v-btn>
</template>
<v-btn
v-if="finalProp"
:class="{alignLeft:isActive}"
fab
dark
large
#click.stop="$emit('func1')"
color="white" >
<v-icon color="#F0BE85">mdi-pencil</v-icon>
</v-btn>
<v-btn
v-if="thirdProp"
:class="{alignLeft:isActive}"
fab
dark
large
#click.stop="$emit('func2')"
color="white">
>
<v-icon color="purple">mdi-delete</v-icon>
</v-btn>
<v-btn
:class="{alignLeft:isActive}"
v-if="secondProp"
fab
dark
large
#click.stop="$emit('func3')"
color="white">
>
<v-icon color="green">mdi-plus</v-icon>
</v-btn>
<v-btn
v-if="firstProp"
:class="{alignLeft:isActive}"
fab
dark
large
#click.stop="$emit('func4')"
color="white">
>
<v-icon color="red">home</v-icon>
</v-btn>
</v-speed-dial>
</v-card>
</template>
<script>
export default {
name: 'FloatingButton',
props: {
firstProp: Boolean,
secondProp: Boolean,
thirdProp: Boolean,
finalProp: Boolean
},
data: () => ({
direction: 'top',
fab: false,
right: true,
bottom: true,
transition: 'scale-transition',
isActive: false,
backgroundColor: false,
check:true
}),
methods: {
toggleButton:function() {
this.isActive = !this.isActive
this.backgroundColor = !this.backgroundColor
}
},
}
</script>
<style scoped>
.is_active {
min-width:120px
/* width: 380px;
height: 70px;
border-radius: 36px;
margin:5px; */
}
.is_active span {
font-size: 18px;
letter-spacing: 0px;
}
.create {
min-width: 100%;
min-height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 4;
background:rgba(0,0,0,0.4);
color:rgba(0,0,0,0.8);
}
}
</style>
App. vue
<template>
<v-app>
<Test :firstProp=a :secondProp=b :thirdProp=c :lastProp=d />
</v-app>
</template>
<script>
import Test from './components/Test'
export default {
name: 'App',
components: {
Test
},
data(){
return{
a:true,
b:true,
c:true,
d:true
}
}
};
</script>
I don't see what's wrong. Can you check this reproduction? I mostly only changed the casing of attributes on <Test> element.
Vue.component('Test', {
template: `
<v-card :class="{create: backgroundColor }">
<v-speed-dial
:bottom="true"
:right="true"
:direction="direction"
:transition="transition"
fixed
>
<template v-slot:activator>
<v-btn
:class="{is_active:isActive}"
color="#C6002B"
fab
dark
#click="toggleButton"
x-large
>
<v-icon>{{isActive? 'mdi-close' : 'mdi-account-circle'}}</v-icon>
</v-btn>
</template>
<v-btn
v-if="finalProp"
:class="{alignLeft:isActive}"
fab
dark
large
#click.stop="$emit('func1')"
color="white" >
<v-icon color="#F0BE85">mdi-pencil</v-icon>
</v-btn>
<v-btn
v-if="thirdProp"
:class="{alignLeft:isActive}"
fab
dark
large
#click.stop="$emit('func2')"
color="white">
>
<v-icon color="purple">mdi-delete</v-icon>
</v-btn>
<v-btn
:class="{alignLeft:isActive}"
v-if="secondProp"
fab
dark
large
#click.stop="$emit('func3')"
color="white">
>
<v-icon color="green">mdi-plus</v-icon>
</v-btn>
<v-btn
v-if="firstProp"
:class="{alignLeft:isActive}"
fab
dark
large
#click.stop="$emit('func4')"
color="white">
>
<v-icon color="red">home</v-icon>
</v-btn>
</v-speed-dial>
</v-card>
`,
props: {
firstProp: Boolean,
secondProp: Boolean,
thirdProp: Boolean,
finalProp: Boolean
},
data: () => ({
direction: 'top',
fab: false,
right: true,
bottom: true,
transition: 'scale-transition',
isActive: false,
backgroundColor: false,
check:true
}),
methods: {
toggleButton: function() {
this.isActive = !this.isActive
this.backgroundColor = !this.backgroundColor
}
},
})
Vue.config.productionTip = false
new Vue({
el: '#app',
vuetify: new Vuetify(),
data(){
return{
a:true,
b:true,
c:true,
d:true
}
}
});
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<style scoped>
.is_active {
/*min-width:120px
width: 380px;
height: 70px;
border-radius: 36px;
margin:5px; */
}
.is_active span {
font-size: 18px;
letter-spacing: 0px;
}
.create {
min-width: 100%;
min-height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 4;
background:rgba(0,0,0,0.4);
color:rgba(0,0,0,0.8);
}
}
</style>
</head>
<body>
<div id="app">
<v-app>
<Test :first-prop="a" :second-prop="b" :third-prop="c" :last-prop="d" />
</v-app>
</div>
</body>
</html>
I'm trying to redirect to a Menu after pressing a button, I have followed this tutorial
but it's not working,
When Pressing my button the url updates, but stays in the same view, it's also adding /#/ to my url instead of following what I coded in routes.js
I'm getting the next error on console
Uncaught (in promise)
NavigationDuplicated {
_name: "NavigationDuplicated",
name: "NavigationDuplicated",
message: "Navigating to current location ("/menu") is not allowed", stack:
When pressing the button the url turns into http://localhost:8080/#/menu instead of
http://localhost:8080/menu
If I manually type the url http://localhost:8080/menu turns into this
http://localhost:8080/menu/#/
Please help, I'm fairly new to vuejs
This is the structure of my project
main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import vuetify from './plugins/vuetify'
import routes from './routes'
import 'roboto-fontface/css/roboto/roboto-fontface.css'
import '#mdi/font/css/materialdesignicons.css'
Vue.config.productionTip = false
Vue.use(VueRouter)
const router = new VueRouter({routes});
new Vue({
render: h => h(App),
router,
vuetify
}).$mount('#app')
App.vue
<template>
<div id="app">
<Home/>
</div>
</template>
<script>
import Home from './views/Home.vue'
import 'material-design-icons-iconfont/dist/material-design-icons.css';
export default {
name: 'App',
components: {
Home
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
my routes.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
import TraceabilityMenu from './views/TraceabilityMenu.vue'
Vue.use(VueRouter)
const routes = [
{ path: '/', component: Home, name: 'home' },
{ path: '/menu', component: TraceabilityMenu, name: 'traceability-menu' },
{path: '/about', component: About, name: 'about'}
]
export default routes;
My Home.vue which is the first view to load(by the App.vue)
<template>
<v-app id="inspire">
<v-app-bar app color="indigo" dark>
<v-toolbar-title>Project Traceability</v-toolbar-title>
<template>
<v-spacer />
<v-btn color="primary" #click="showPopupLogin()" :to="{ name: 'login'}" >Ingresar</v-btn>
</template>
</v-app-bar>
<PopupLogin v-show="showLogin"/>
<v-content>
<v-container
class="fill-height"
fluid
>
<v-row
align="center"
justify="center"
>
<v-col class="text-center">
</v-col>
</v-row>
</v-container>
</v-content>
<v-footer
color="indigo"
app
>
</v-footer>
</v-app>
</template>
<script>
import PopupLogin from '#/components/PopupLogin.vue';
export default {
props: {
source: String,
},
data: () => ({
showLogin : false
}),
components: {
PopupLogin,
},
methods: {
showPopupLogin() {
this.showLogin = !this.showLogin
}
}
}
</script>
The component PopupLogin
<template>
<v-app id="inspire">
<v-content>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="4">
<v-card class="elevation-12">
<v-toolbar color="primary" dark flat >
<v-toolbar-title>Iniciar sesión</v-toolbar-title>
<v-spacer />
<v-tooltip bottom>
</v-tooltip>
</v-toolbar>
<v-card-text>
<!-- Formulario de login-->
<v-form v-model="validForm" ref="formLogin">
<v-text-field
required
label="Usuario"
:rules="nameRules"
name="login"
type="text"
v-model="existingUser.username"/>
<v-text-field
required
id="password"
prepend-icon="lock"
label="Contraseña"
name="password"
type="password"
v-model="existingUser.password"/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn color="primary" #click="loginUser()">Ingresar</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'PopupLogin',
props: {
source: String
},
data: () => ({
validForm : false,
//objetos
existingUser : {}
}),
methods: {
//Funcion que llamara al servicio de login en backend
loginUser() {
this.$router.push({path: '/menu'});
}
}
}
</script>
TraceabilityMenu.vue the view which I'm trying to render after the press of the button Login
<template>
<v-app id="inspire">
<div>RENDER ME!</div>
</v-app>
</template>
<script>
export default {
props: {
source: String,
},
data: () => ({
drawer: null,
}),
}
</script>
On your main.js file try changing
const router = new VueRouter({routes});
to
const router = new VueRouter({routes, mode: 'history'});
Edit: Also check if you have included the router-view tag on your root component App.vue.
I have a problem related to Vue.js probs (I guess). I have a chart which fetches data in the created() from an API which looks like this:
<template>
<div>
<highcharts if="data.length > 0" :options="chartOptions" ></highcharts>
</div>
</template>
<script>
import { Chart } from "highcharts-vue";
import Highcharts from "highcharts";
import exportingInit from "highcharts/modules/exporting";
exportingInit(Highcharts);
export default {
props: {
partsdata: {
type: Array
}
},
created() {
this.$http.get('http://127.0.0.1:5000/data/')
.then(response => {
var result = JSON.parse(response.data)
return result
}).then(data => {
var result = data.Fare
console.log(result) // eslint-disable-line no-console
this.chartOptions.series[0].data = [] // Array wieder zurücksetzen
for(var key in result) {
this.chartOptions.series[0].data.push(result[key]) // eslint-disable-line no-console
}
})
},
components: {
highcharts: Chart
},
data() {
return {
data: [],
chartOptions: {
title: {
text: 'Title aligned left',
align: 'left',
x: 0
},
series: [{
type: "column",
data: [] // sample data
}]
}
}
}
};
</script>
<style scoped>
.btn-primary {
padding: 10px 20px;
background: white;
color: black;
margin-top: 15px;
margin-bottom: 15px;
border-radius: 5px;
}
</style>
This is my parent component, where I use my Charts, which works fine.
<template>
<v-container grid-list-md text-xs-center>
<v-layout row wrap>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
<v-flex xs6>
<v-card dark color="white">
<v-card-text class="px-0">
<Highcharts></Highcharts>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import Highcharts from "./Header.vue"
export default {
components: {
Highcharts: Highcharts
}
}
</script>
<style scoped>
</style>
However, what I want is to fetch the data from different Endpoints without creating a ton of new components. Is there a way to use the created() lifecycle hook as props or are there any other clever solutions how to archieve this?
I need a way to use list of v-card from Vuetify like Bootstrap Card Columns
Possible solution:
Pardon me, I couldn't plunkr the code :(
This is what I did.
Update: Unfortunately this messes up with the v-ripple directive
<template>
<v-container grid-list-md>
<div class="v-card-columns">
<v-card tile v-for="post in posts" :key="post.id">
<v-card-title primary-title>
<h3 headline>
{{post.title}}
</h3>
</v-card-title>
<v-card-text>
{{post.body}}
</v-card-text>
</v-card>
</div>
</v-container>
</template>
<script>
import axios from "axios";
export default {
name: "All",
data() {
return {
posts: []
}
},
mounted() {
axios
.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
this.posts = res.data
})
}
}
</script>
<style scoped>
.v-card-columns .v-card {
margin-bottom: 0.75rem;
}
#media (min-width: 576px) {
.v-card-columns {
-webkit-column-count: 3;
-moz-column-count: 3;
column-count: 3;
-webkit-column-gap: 1.25rem;
-moz-column-gap: 1.25rem;
column-gap: 1.25rem;
orphans: 1;
widows: 1;
}
.v-card-columns .v-card {
display: inline-block;
width: 100%;
}
}
</style>
Pardon me, I couldn't plunkr the code :(
This is what I did.
Update: Unfortunately this messes up with the v-ripple directive
<template>
<v-container grid-list-md>
<div class="v-card-columns">
<v-card tile v-for="post in posts" :key="post.id">
<v-card-title primary-title>
<h3 headline>
{{post.title}}
</h3>
</v-card-title>
<v-card-text>
{{post.body}}
</v-card-text>
</v-card>
</div>
</v-container>
</template>
<script>
import axios from "axios";
export default {
name: "All",
data() {
return {
posts: []
}
},
mounted() {
axios
.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
this.posts = res.data
})
}
}
</script>
<style scoped>
.v-card-columns .v-card {
margin-bottom: 0.75rem;
}
#media (min-width: 576px) {
.v-card-columns {
-webkit-column-count: 3;
-moz-column-count: 3;
column-count: 3;
-webkit-column-gap: 1.25rem;
-moz-column-gap: 1.25rem;
column-gap: 1.25rem;
orphans: 1;
widows: 1;
}
.v-card-columns .v-card {
display: inline-block;
width: 100%;
}
}
</style>