Vuetify with VueDraggable : v-img is not loaded as expected - vue.js

I am trying to use vueDraggable with vuetify, to create a photo gallery which allowed to rearrange the order.
Without Draggable, the v-img is loading the image correctly.
While after I added the draggable, the images is not going to be loaded.
To test the images path is correct, I added a below the and the are loaded properly.
https://codepen.io/brian661/pen/zVygap/
<template xmlns:v-slot="http://www.w3.org/1999/XSL/Transform">
<v-layout>
<v-flex xs12>
<v-card>
<v-container grid-list-sm fluid>
<v-layout row wrap>
<draggable
class="draggableArea"
:list="photos"
:group="{ name: 'photo', pull: 'clone', put: false }"
:sort= false
>
<v-flex
v-for="photo in photos"
:key=photo.name
xs3
d-flex
>
{{photo.name}}
<v-card flat tile class="d-flex">
// this is not able to be loaded
<v-img
:contain="true"
:src=photo.img
:lazy-src=photo.img
class="grey lighten-2"
>
// this is loaded without problem
<img :src=photo.img class="Photo"/>
<template v-slot:placeholder>
<v-layout
fill-height
align-center
justify-center
ma-0
>
<v-progress-circular indeterminate color="grey lighten-5"></v-progress-circular>
</v-layout>
</template>
</v-img>
</v-card>
</v-flex>
</draggable>
</v-layout>
</v-container>
</v-card>
</v-flex>
</v-layout>
</template>
<script>
import draggable from 'vuedraggable';
export default {
name: "AvailablePhoto",
components: {
draggable,
},
data() {
return {
photos : [ {name: "photo1", img: "https://somewhere/1.png"},
{name: "photo2", img: "https://somewhere/2.png"},
{name: "photo3", img: "https://somewhere/3.png"},
{name: "photo3", img: "https://somewhere/4.png"}]
}
}
}
</script>
<style scoped>
.draggableArea {
display: flex;
}
.Photo {
max-width: 100%;
max-height: 100%;
}
</style>

Try
<v-img :src="require('photo.img')"></v-img>
instead of
<v-img :src="photo.img"></v-img>
Vue loader converts relative paths into require functions automatically for you. Unfortunately, this is not the case when it comes to custom components. You can circumvent this issue by using require. If you're using Vuetify as a Vue-CLI 3 plugin, you can edit your project's vue.config.js file by modifying the options for vue-loader.
// Incorrect
<v-img src="../path/to/img" />
// Correct
<v-img :src="require('../path/to/img')" />
Source: Vuetify
Hope it helps.

Related

Nuxt, Vuetify skeleton loader while image loads

I cannot find one proper example of loading an image with a skeleton loader. I'm using nuxt + Vuetify and I'm trying to use a simple image with a skeleton loader.
Here is my code.
<template>
<v-skeleton-loader v-if="loading" :loading="loading" type="image">
<v-card
v-show="loaded"
class="ma-auto elevation-4"
shaped
color="darkgreen"
width="500"
flat
>
<v-img
src="/products/em-lucky-combo.jpg"
max-width="500"
#load="hasLoaded"
>
</v-img>
</v-card>
</v-skeleton-loader>
</template>
my method
<script>
export default {
methods: {
hasLoaded() {
console.log('Image finished loading')
this.loading = false
this.loaded = true
},
},
}
</script>
But as I said the method never gets called when it is inside the v-card tag or directly in the skeleton loader.
I have tried using #load, I have tried using mounted hooks like below.
<script>
export default {
mounted() {
const readyHandler = () => {
if (document.readyState === 'complete') {
console.log('Document Rendered')
this.loading = false
this.loaded = true
document.removeEventListener('readystatechange', readyHandler)
}
}
document.addEventListener('readystatechange', readyHandler)
readyHandler() // in case the component has been instantiated lately after loading
},
}
</script>
But nothing seems to work properly or elegantly. Is it just not possible? The moment the <v-img> tag is inside the skeleton loader or inside a v-card inside a skeleton loader it never gets rendered no matter what I do. One suggestion was to use a slot. I'm guessing it has something to do with slots but I do not understand how to use these.
I tried doing something like this.
<template v-slot:default>
<v-card
class="ma-auto elevation-4"
shaped
color="darkgreen"
width="500"
transition="fade-transition"
flat
>
<v-img
src="/products/em-lucky-combo.jpg"
max-width="500"
transition="fade-transition"
#load="imageLoaded"
></v-img>
</v-card>
</template>
I tried using the code below.
How to make v-skeleton loader inside v-for in Vuetify
<v-img>
<template #placeholder>
<v-sheet>
<v-skeleton-loader />
</v-sheet>
</template>
</v-img>
At the end, OP had enough of using just some dimensions and a lazy loader image. Since images were local, there was no need for a skeleton.
<template>
<v-card class="ma-auto elevation-4" flat shaped width="500">
<v-img
aspect-ratio="1"
class="grey lighten-2"
lazy-src=""
max-height="350"
max-width="500"
src="/"
transition="fade-transition"
>
<template v-slot:placeholder>
<v-row
align="center"
class="fill-height ma-0"
justify="center"
>
<v-progress-circular
color="grey lighten-5"
indeterminate
></v-progress-circular>
</v-row>
</template>
</v-img>
</v-card>
</template>
Thanks to #kissu I realised my approach was wrong and unnecessary . I ended up giving my images some set dimensions and using a lazy image loader with a placeholder slot which works just fine. As he mentioned my images are loaded locally so it wont really work in my case.
What I did in the end.
<template>
<v-card class="ma-auto elevation-4" flat shaped width="500">
<v-img
aspect-ratio="1"
class="grey lighten-2"
lazy-src=""
max-height="350"
max-width="500"
src="/"
transition="fade-transition"
>
<template v-slot:placeholder>
<v-row
align="center"
class="fill-height ma-0"
justify="center"
>
<v-progress-circular
color="grey lighten-5"
indeterminate
></v-progress-circular>
</v-row>
</template>
</v-img>
</v-card>
</template>

displaying highlighted list items in vuetify 2.x

I'm writing my first app in vuetify 2.x. The app is displaying a list of items which are objects with a text (item.t) string field and a checked (item.c) boolean field.
I would like to display checked items in the current theme color and the unchecked items in the opposite theme color (highlighted). It thus depends on the value of the item.c field value.
I assume that changing the them of the list item will kind of reverse the colors of its content. Black <-> white.
How could I do that ?
This is my list component:
<template>
<v-list dense>
<template v-for="(item, index) in items">
<v-list-item :key="item.r">
<v-list-item-content class="font-weight-medium">
<v-layout>
<v-row align="center">
<v-col cols="2">
<v-row no-gutters justify="end">
{{ item.n }}
</v-row>
</v-col>
<v-col cols="10" class="px-0">
<v-row no-gutters>{{ item.t }}</v-row>
</v-col>
</v-row>
</v-layout>
</v-list-item-content>
</v-list-item>
<v-divider
v-if="index < items.length - 1"
:key="`divider-${index}`"
></v-divider>
</template>
</v-list>
</template>
<script>
export default {
name: "itemList",
computed: {
items() {
return this.$store.getters.currentListItems;
},
},
};
</script>
I tried many things without success and couldn't find an example how to do that.
Edit: since the items contains just text and no icons, maybe it's enough the change the background and text color. The nice thing of theme is that it also reverse icons.
You can do it by different ways by v-menu by v-select or v-combo-box
and here you can use option multiple
https://vuetifyjs.com/en/components/combobox/#dense
but i think you need to use combo-box
and here you have slots
freedom of thought
I finally solved it. It's a bit hacky but it does the job.
The solution is to use a style computed with a method receiving the item object as argument. The hacky part is the way I change the style.
The highlighted text and background colors swap with the dark or light setting. Switching the them switches all the colors.
<template>
<v-list dense>
<template v-for="(item, index) in items">
<v-list-item :key="item.r" v-bind:style="highlighted(item)">
<v-list-item-content class="font-weight-medium">
<v-layout>
<v-row align="center">
<v-col cols="2">
<v-row no-gutters justify="end">
{{ item.n }}
</v-row>
</v-col>
<v-col cols="9" class="px-0">
<v-row no-gutters>{{ item.t }}</v-row>
</v-col>
</v-row>
</v-layout>
</v-list-item-content>
</v-list-item>
<v-divider
v-if="index < items.length - 1"
:key="`divider-${index}`"
></v-divider>
</template>
</v-list>
</template>
<script>
export default {
name: "itemList",
computed: {
items() {
return this.$store.getters.currentListItems;
},
},
methods: {
highlighted(item) {
let backColor = "white";
let textColor = "#1E1E1E";
if (
(this.$vuetify.theme.dark && item.c) ||
(!this.$vuetify.theme.dark && !item.c)
) {
[textColor, backColor] = [backColor, textColor];
}
return {
"background-color": backColor,
color: textColor,
};
},
},
};
</script>

Vuejs error: Property or method "products" is not defined on the instance but referenced during render

Im new too nuxtjs and vuex, unfortunately i got some errors trying too fetch my data from vuex;
[Vue warn]: Property or method "products" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
./pages/index.vue
<template>
<div>
<TheCarousel />
<SellingItems
v-for="prod in products"
:key="prod.id"
:id="prod.id"
:image="prod.image"
:name="prod.name"
:price="prod.price"
/>
</div>
</template>
<script>
import TheCarousel from '../components/TheCarousel'
import SellingItems from '../components/sellingItems'
import { mapGetters } from 'vuex'
export default {
components: {
TheCarousel,
SellingItems
},
computed: {
// products(){
// return this.$store.getters['prods/products']
// }
...mapGetters(['prods/products'])
}
}
</script>
./components/sellingItems.vue
<template>
<v-container class="my-5">
<h2>Top Selling Items</h2>
<v-divider class="my-4"></v-divider>
<v-row>
<v-col
sm="6"
md="4"
v-for="product in products"
:key="product.name">
<v-card outlined>
<v-img :src="product.image" height="200px" />
<v-card-title> {{ product.name}} </v-card-title>
<v-card-subtitle> ${{ product.price }}</v-card-subtitle>
<v-card-actions>
<v-btn color="success" outlined to="#">
<v-icon small left> add </v-icon>
Add to Cart
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
props: ['id', 'image', 'name', 'price'],
}
</script>
<style>
</style>
store/modules/product.js
export default {
namsespaced: true,
state(){
return{
products: [
{
id: '1',
name: 'Playstation 4',
price: 200,
image: 'img/ps4.jpg'
},
{
id: '2',
name: 'Playstation 5',
price: 700,
image: 'img/ps5.jpg'
},
{
id: '3',
name: 'Nintendo wii',
price: 100,
image: 'img/wii.jpg'
}
]
}
},
getters: {
products(state){
return state.products;
}
}
}
You are passing in each item's properties into SellingItem but then in selling items, you are looping through products which does not exist in that component. Did you mean to use the passed in properties instead?
To use those passed in properties, reference them the same as if they were defined in data or computed:
<v-row>
<v-col sm="6" md="4">
<v-card outlined>
<v-img :src="image" height="200px" />
<v-card-title> {{name}} </v-card-title>
<v-card-subtitle> ${{price}}</v-card-subtitle>
<v-card-actions>
<v-btn color="success" outlined to="#">
<v-icon small left> add </v-icon>
Add to Cart
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
Your mapGetters is not defined correctly from within index.vue. Try this:
...mapGetters('products', ['products'])
And you would reference it like this.products.
The namespace would only be needed, from my understanding, if this Vuex store were nested in a parent store or if the getter was being called from within another Vuex store. For your example, calling with namespace from within a separate Vuex store's action would look like: const value = context.rootGetters['products/products']
Having said all of that, if your getter just returns the state item itself without any other logic, you might as well call the state item directly instead of going through a getter.

Center content in Vuetify project / Grid system

I am starting learning Vue.js and I got a problem with understanding Vuetify's grid system. Now I want to center that content to the center. I've read docs and tried for many ways, but positioning elements it is still random, I would say.
Can anybody explain me v-content, v-layout and v-flex?
Here's code.
<template>
<v-container>
<v-layout text-center>
<v-flex xs4 >
<v-img
:src="require('../assets/logo.svg')"
class="my-3"
contain
height="200"
>
</v-img>
<v-text-field v-model="newPrice" placeholder="Add price"></v-text-field>
<v-btn #click="addPrices">Add price </v-btn>
<v-text-field v-model="newVolume" placeholder="Add volume"></v-text-field>
<v-btn #click="AddVolume">Add volume </v-btn>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
export default {
name: 'MainPage',
data () {
return {
prices: [],
volumes: [],
newPrice: '',
newVolume: ''
}
},
methods: {
addPrices () {
this.prices.push(this.newPrice)
},
addVolume () {
this.volumes.push(this.newVolume)
}
}
}
</script>
So vuetify's grid system takes advantage of CSS flexbox. v-container determines the spacing around and between elements; v-layout determines the flex axis/direction/order of the elements, and v-flex determines how big the elements will be in the 12-point grid system.
To center something, try the below:
<v-layout row justify-center></v-layout>
This is equivalent to:
.layout {
display: flex;
flex-direction: row;
justify-content: center;
}
There is no prop in the v-layout component named text-center so I'm guessing that's why it's not centering.

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