Vue.js: How to update element's position - vue.js

I want to move an element while scrolling so the element is always in the screen.
But the position isn't updated.
What I am doing is this.
<v-card class="item" :style="{ top: distance }">
</v-card>
...
data() {
return {
distance: 0,
}
}
methods: {
handleScroll() {
this.distance = window.scrollY
},
},
created() {
window.addEventListener('scroll', this.handleScroll)
},
destroyed() {
window.removeEventListener('scroll', this.handleScroll)
},
}
...
.item {
position: absolute;
}
How can I do it?

Your element should have an absolute position and add the units as #Nikos mentioned :
<v-card class="item" style="position:absolute" :style="{ top: distance+'px' }">
</v-card>

Related

How can I change the backgroundColor of a row in a beufy datatable with a given color for each row?

I'm new to web technologies and I'm stuck this this problem: I would like to be able to change the color of a row dynamically.
I tried different things with my knowledge and none worked well. I have a json with information for the table with the color code inside (ex: #D465F2). I wasn't able to find a way to color the entire row in one time dynamically. I can do it with predetermined css but it's not what I need.
The only way it worked is this one:
Template:
<template>
<div>
<p class="title">logs</p>
<b-table
:data="loginfo"
:sticky-header="stickyHeaders"
height="1200px"
:row-class="() => 'relative'"
>
<b-table-column v-slot="props" field="log_id" label="color">
<div class="colored-cell" :style="{ backgroundColor: props.row.color }">
<p>{{ props.row.color }}</p>
</div>
</b-table-column>
</b-table>
</div>
</template>
Script:
<script>
import axios from 'axios'
export default {
props: {
id: Number,
state: Number,
},
data() {
return {
loginfo: [],
stickyHeaders: true,
dateSearchable: false,
}
},
watch: {
id(newId) {
this.newLog()
},
},
methods: {
async newLog() {
await axios
.get('http://127.0.0.2:5001/api/log?sequence=' + this.id)
.then((response) => (this.loginfo = response.data.data))
},
},
}
Exemple of json send to the script:
"data": [
{
"color": "#15986E",
"log_id": 25,
"logged_at": "2022-05-17 16:27:00.015",
},
and this is my css:
.relative {
position: relative;
}
.colored-cell {
position: absolute;
top: 0;
left: 0;
padding: 8px 12px;
}
With this code I color each cell one by one but it makes the table unreadable and all broken. Everything is on top of each other. TableBroken Screen
If anyone have the solution to make the table looks great again/color row dynamically or anything that can help me, another lib which allows me to change the row color dynamically.
Just use a td-attrs prop as documented
<b-table>
:data="loginfo"
:sticky-header="stickyHeaders"
height="1200px"
>
<b-table-column v-slot="props" field="log_id" label="color" :td-attrs="columnTdAttrs">
{{ props.row.color }}
</b-table-column>
</b-table>
</div>
export default {
methods: {
columnTdAttrs(row, column) {
return {
style: {
backgroundColor: row.color
}
}
}
},
}

Vue testing with Vuetify. Cannot read property 'title' of undefined

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: '',
}
});

Vue Overlay for specific card

I am using Vue with Vuetify and I used a for loop to render 8 different cards. Each card has its own overlay which shows its own image when clicked, however when I click one card, all of the overlays get set off instead of the overlay for just the specific card. How should I go about doing this? Would I have to add an ID for each card?
<script>
export default {
data: () => ({
arts: [
require("#/assets/art1.jpg"),
require("#/assets/art2.jpg"),
require("#/assets/art3.jpg"),
require("#/assets/art4.jpeg"),
require("#/assets/art5.jpg"),
require("#/assets/art6.jpg"),
require("#/assets/art7.jpg"),
require("#/assets/art8.jpg")
],
absolute: true,
opacity: 1,
overlay: false,
})
};
</script>
<template>
<div style="width: 100%; height: 100%">
<v-container fluid style="height:100%; background-color:#fc8276; width:25%; float:left"></v-container>
<v-container style=" height: 100%; width: 75%; float:right" fluid>
<h1>Portfolio</h1>
<v-card
v-for="art in arts"
class="ma-5"
v-bind:key="art"
style="width: 15em; height: 15em; display:inline-flex"
>
<v-img :src="art" v-on:click="overlay=true"></v-img>
<v-overlay :absolute="absolute" :opacity="opacity" :value="overlay">
{{art}}
</v-overlay>
</v-card>
</v-container>
</div>
</template>
This is untested, but something like this should work... basically restructure your data a bit. Right now you have one overlay data boolean. Whenever you are setting this to true, you are setting it to true for everything that is using that same boolean... in your case all of your overlayables are looking at that same boolean.
Also I would recommend moving your styles into CSS classes, it will make this way more readable...
You need to update your HTML to this:
<template>
<div style="width: 100%; height: 100%">
<v-container fluid style="height:100%; background-color:#fc8276; width:25%; float:left"></v-container>
<v-container style=" height: 100%; width: 75%; float:right" fluid>
<h1>Portfolio</h1>
<v-card
v-for="(art, index) in arts"
class="ma-5"
v-bind:key="index"
style="width: 15em; height: 15em; display:inline-flex"
>
<v-img :src="art" v-on:click="showOverlay(art, index)"></v-img>
<v-overlay :absolute="absolute" :opacity="opacity" :value="art.overlay">
{{art.asset}}
</v-overlay>
</v-card>
</v-container>
</div>
</template>
Then change your data a bit:
data: () => ({
arts: [
{ asset: require("#/assets/art1.jpg"), overlay: false },
{ asset: require("#/assets/art2.jpg"), overlay: false },
{ asset: require("#/assets/art3.jpg"), overlay: false },
{ asset: require("#/assets/art4.jpeg"), overlay: false },
{ asset: require("#/assets/art5.jpg"), overlay: false },
{ asset: require("#/assets/art6.jpg"), overlay: false },
{ asset: require("#/assets/art7.jpg"), overlay: false },
{ asset: require("#/assets/art8.jpg"), overlay: false }
],
absolute: true,
opacity: 1,
})
Then in your methods:
methods: {
showOverlay(art, index) {
this.arts.map((a) => a.overlay = false);
this.arts[index].overlay = true;
}
}
I would make a new component which holds the v-card, so each one would have its own overlay data property. But having it all in one component with arts.length overlay variables works as well

How to place v-text-field error message in vuetifyjs?

I have v-text-field and I can't able to display the error message out of v-text-field dom position. in vuetify documentation there is not reference to this issue.
Is it possible to have the error message near the input?
Actual:
Expected:
<template>
<v-app>
<v-content>
<playground></playground>
<v-text-field
style="width:120px;"
class="numer"
:rules="[rules.required, rules.min, rules.max]"
v-model="numValue"
type="number"
append-outer-icon="add"
#click:append-outer="increment"
prepend-icon="remove"
#click:prepend="decrement"
></v-text-field>
{{numValue}}
</v-content>
</v-app>
</template>
<script>
import Playground from "./components/Playground";
export default {
name: "App",
components: {
Playground
},
data: function() {
return {
numValue: 0,
form: {
min: 2,
max: 10
},
rules: {
required: value => !!value || "Required.",
min: v => v >= this.form.min || `The Min is ${this.form.min}`,
max: v => v <= this.form.max || `The Max is ${this.form.max}`
}
};
},
methods: {
increment() {
if (this.numValue < this.form.max) {
this.numValue = parseInt(this.numValue, 10) + 1;
}
},
decrement() {
if (this.numValue > this.form.min) {
this.numValue = parseInt(this.numValue, 10) - 1;
}
}
}
};
</script>
<style>
.numer {
flex: 0 0 auto;
margin: 0;
padding: 0;
}
.numer input {
text-align: center;
}
</style>
The code in codesandbox
I know I'm late but if someone needs help:
Put this field inside a form.
<v-form ref="form">
<v-text-field
style="width:120px;"
class="numer"
:rules="[rules.required, rules.min, rules.max]"
v-model="numValue"
type="number"
append-outer-icon="add"
#click:append-outer="increment"
prepend-icon="remove"
#click:prepend="decrement"
></v-text-field>
</v-form>
Then call anywhere on template:
<div #click="$refs.form.validate()">Validate it!</div>
Or on the functions:
methods: {
foo() {
this.$refs.form.validate()
}
}

How to draw shape from path in vue konva?

I am trying to use konvajs inside vue. I want to draw an object from path data, but i don't know how to do it. My main purpose is to get the path data from server, but first i would like to see some drawing in action.
Thank you!
All help appreciated.
<div>
<v-container>
<v-layout align-end justify-center row fill-height>
<v-flex xs12>
<v-stage :config="configKonva">
<v-layer>
<v-shape :config="configShape"/>
</v-layer>
</v-stage>
</v-flex>
</v-layout>
</v-container>
</div>
</template>
<script>
export default {
data() {
return {
configShape: {},
configKonva: {
width: 200,
height: 200
}
};
},
methods: {},
mounted() {
this.configShape = new Konva.Path({
fill: "#00D2FF",
stroke: "black",
strokeWidth: 4,
data: "m0,0L100,100L0,100L0,0",
sceneFunc: function(context, shape) {
// special Konva.js method
context.fillStrokeShape(shape);
}
});
}
};
</script>```
It is not recommended to use sceneFunc for built-in shapes (such as Konva.Path).
Take a look into shapes tutorial for vue-konva https://konvajs.org/docs/vue/Shapes.html.
If you want to create a Konva.Path you need to use v-path component:
<v-path
:config="{
x: 200,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
data: 'm0,0L100,100L0,100L0,0',
}"
/>
Demo: https://codesandbox.io/s/32xxoon18p
You you want to have full control of drawing you can use custom shapes: https://konvajs.org/docs/vue/Custom_Shape.html
I could make it work. I don't think, that this is the correct way, but now at least it's working
mounted() {
this.configShape = new Konva.Path({
fill: "#00D2FF",
stroke: "black",
strokeWidth: 4,
data: "m0,0L100,100L0,100L0,0",
sceneFunc: function(context, shape) {
let arr = shape.attrs.dataArray;
context.beginPath();
for (var i = 0; i < arr.length; i++) {
if (arr[i].command == "M") {
context.moveTo(arr[i].points[0],arr[i].points[1]);
} else if (arr[i].command == "L") {
context.lineTo(arr[i].points[0],arr[i].points[1]);
}
}
context.closePath();
// special Konva.js method
context.fillStrokeShape(shape);
}
});
}