v-for list in dialog disappears after dialog closes - vue.js

I have a dialog that contains v-navigation-drawer and a list is rendered inside.
It works fine on the first dialog open, but after closing it, it won't show the list the second time.
My navigation:
<v-navigation-drawer floating height="140px" class="w-100">
<v-list dense rounded>
<v-divider></v-divider>
<v-list-item-group class="mt-3" color="indigo" mandatory>
<v-list-item v-for="(desc, index) in dataList" :key="index" #click="getDesc(desc)">
<v-list-item-action-text class="pe-4">{{ desc.title }}</v-list-item-action-text>
<v-list-item-content>
</v-list-item-content>
<v-list-item-icon>
<v-icon small color="red" class="pe-2" #click="deleteDesc(desc.eid)">mdi-delete</v-icon>
</v-list-item-icon>
</v-list-item>
<v-list-item v-if="!dataList.length">
مرحله ای وجود ندارد
</v-list-item>
</v-list-item-group>
</v-list>
</v-navigation-drawer>
My data:
data() {
return {
dialog: false,
dataList: [
{
eid: "597d2l6orwm6",
title: "توضیحات اولیه",
text: "لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است،" +
" چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است،" +
" و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود " +
"ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده،" +
" شناخت فراوان جامعه و متخصصان را می طلبد، تا با نرم افزارها شناخت بیشتری را" +
" برای طراحان رایانه ای علی الخصوص طراحان خلاقی، و فرهنگ پیشرو در زبان فارسی ایجاد کرد، " +
"در این صورت می توان امید داشت که تمام و دشواری موجود در ارائه راهکارها،" +
" و شرایط سخت تایپ به پایان رسد و زمان مورد نیاز شامل حروفچینی دستاوردهای اصلی،" +
" و جوابگوی سوالات پیوسته اهل دنیای موجود طراحی اساسا مورد استفاده قرار گیرد.",
}
],
descObj: {}
}
}
First time opening dialog:
Second time opening dialog:

Related

vue.js not responding to property of object changing

<v-container class="text-center hyp-container pa-4">
<v-row>
<button #click="toggleForm">Add new</button>
</v-row>
<v-row>
<v-dialog v-model="showConfirmDelete" width="500">
<v-card>
<v-card-title class="headline grey lighten-2">
Confirm
</v-card-title>
<v-card-text>
<v-spacer></v-spacer>
Are you sure you want to delete this target?
</v-card-text>
<v-card-actions>
<v-btn color="secondary" #click="showConfirmDelete = false"
>Cancel</v-btn
>
<v-spacer></v-spacer>
<v-btn color="primary" #click="doDeleteTarget">Delete</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="showTargetForm" width="800">
<v-card>
<form #submit="saveTarget">
<v-card-title class="headline grey lighten-2">
Add Slack target
</v-card-title>
<v-card-text>
<v-spacer></v-spacer>
<v-text-field
label="Target Name"
v-model="target.name"
#keyup="modifyTargetName"
></v-text-field>
<v-text-field
label="Webhook URL"
v-model="target.webhook"
></v-text-field>
<v-text-field
label="Slack Channel"
v-model="target.channel"
#keyup="modifyTargetChannel"
></v-text-field>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn color="secondary" #click="showTargetForm = false"
>Close</v-btn
>
<v-spacer></v-spacer>
<v-btn color="secondary" #click="target = {}">Clear</v-btn>
<v-btn color="secondary" #click="testTarget">Test</v-btn>
<v-btn color="primary" type="submit">
Save
</v-btn>
</v-card-actions>
</form>
</v-card>
</v-dialog>
</v-row>
<v-row>
<ol>
<li v-for="target in targets" :key="target.id">
<v-card>
<v-card-text>
<v-card-actions>
<v-btn icon #click="toggleShow(target)">
<v-icon>{{
target.show ? "mdi-chevron-up" : "mdi-chevron-down"
}}</v-icon>
</v-btn>
<div class="title">
{{ target.name }}
{{ target.show }}
</div>
</v-card-actions>
<v-expand-transition>
<div v-if="target.show">
<v-divider></v-divider>
<v-card-text>
{{ target.channel }}
edit
delete
<div class="truncate">{{ target.webhook }}</div>
</v-card-text>
</div>
</v-expand-transition>
</v-card-text>
</v-card>
</li>
</ol>
</v-row>
</v-container>
</template>
<script>
export default {
name: "PersonalTargetsSelector",
props: {
disabled: {
type: Boolean,
default: false,
},
},
data: () => ({
showTargetForm: false,
target: {},
targets: [],
showConfirmDelete: false,
targetToDelete: {},
show: {},
}),
async mounted() {},
methods: {
toggleShow(target) {
console.log(target.show);
this.target.show = !Boolean(target.show);
console.log(target.show);
},
handleClickCancel: function () {
this.target = {};
this.showTargetForm = false;
this.handlePageTrackerEvent(
EVENT_CATEGORIES.personalTargets,
EVENT_ACTIONS.clicked,
"Edit Mode - Cancel a Personal Target"
);
},
saveTarget(e) {
e.preventDefault();
console.log("Save target....", this.target);
if (this.target.id) {
// update
this.targets.map(
(t) =>
this.targets.find((t) => t.id === this.target.id) || this.target
);
} else {
// create
this.target.id = Math.random().toString(26).slice(2);
this.targets.push(this.target);
}
this.target = {};
this.showTargetForm = false;
},
editTarget(target) {
this.target = target;
this.showTargetForm = true;
},
testTarget() {
console.log("test target...");
},
confirmDeleteTarget(target) {
this.showConfirmDelete = true;
this.targetToDelete = target;
},
doDeleteTarget() {
this.targets = this.targets.filter(
(t) => t.id !== this.targetToDelete.id
);
this.showConfirmDelete = false;
},
modifyTargetChannel(e) {
const { value } = e.target;
console.log(value);
this.target.channel = value.indexOf("#") === -1 ? "#" + value : value;
},
modifyTargetName(e) {
const { value } = e.target;
this.target.name =
value.indexOf("[Personal]") === -1 ? "[Personal] " + value : value;
},
toggleForm(e) {
e.preventDefault();
this.showTargetForm = !this.showTargetForm;
},
handleClickClose() {
this.forceClose = new Date().toISOString();
},
setConfirmationShow(value) {
this.resetConfirmationShow = value;
},
handlePageTrackerEvent(category, action, name) {
let _paq = (window._paq = window._paq || []);
_paq.push(["trackEvent", category, action, name]);
pageTrackerLogger(
"Page Tracker Event " +
category +
" " +
action +
" " +
name +
" logged."
);
},
},
watch: {
"target.show"(newValue) {
console.log(newValue);
},
},
};
</script>
<style>
.v-application ol {
list-style-type: none;
padding: 0;
}
</style>
the log shows its changing but the UI does not update its value when i put it in {{target.show}}
You're updating target through the props passed to function. Instead:
methods: {
toggleShow(target) {
console.log(target.show);
this.target.show = !Boolean(this.target.show);
console.log(target.show);
},
}

Passing array to component, devtools sees it, the component does not

I'm passing an array to a component but the component sees the array as undefined. Here is the parent calling the component...
<FileList ref="files" class="ma-3 pa-0" :passFiles="true" :passedFiles="header.files"></FileList>
Vue devtools sees the array, it is valid. As seen in the screenshot below:
Yet in my created hook in the controller, it shows this.passedFiles as undefined. (this.passFiles, however, shows correctly as true.)
created(){
console.log(this.passFiles,this.passedFiles); //this.passedFiles shows as undefined
window.addEventListener("resize", this.onResize);
},
I dumped the array right before it gets sent to the component, and it is there, see screenshot:
I tried this just to make sure, and it gets passed to the array fine:
:passedFiles="[{0: '1'}]"
I'm pulling my hair out here. Here is the full component, it is long but it shows you everything
<template>
<div>
<div class="text-center pa-10" v-show="loading">
<v-progress-circular
:size="35"
:width="3"
color="primary"
indeterminate
></v-progress-circular>
</div>
<v-data-table
v-show="!loading"
:headers="showActions ? headers : headersRead"
:items="orderedFiles"
:items-per-page="paginate"
:footer-props="{'items-per-page-options':[paginate, 15, 30, 50, 100, -1]}"
:hide-default-footer="oneFileOnly"
class="elevation-1 custom-rounded-box ma-0 pa-0"
ref="aWidth"
:style="forceHeight&&$vuetify.breakpoint.mdAndUp ? 'height:'+forceHeight+'px;' : ''"
>
<template slot="no-data">
<div>There are currently no files here</div>
</template>
<template v-slot:item.description="{item, index}">
<v-row
no-gutters
style="flex-wrap: nowrap;"
>
<v-col
cols="12"
md="11"
class="flex-grow-0 flex-shrink-0"
>
<v-tooltip bottom v-if="item.description">
<template v-slot:activator="{ on, attrs }">
<a
v-if="item.gdoc"
style="text-decoration: none; color: orange;"
v-bind="attrs"
v-on="on"
#click.prevent="gdocDialog = true;editingFile = item"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.description}}
</a>
<a
v-else
:href="'/getFile?id='+item.id"
style="text-decoration: none; color: orange;"
v-bind="attrs"
v-on="on"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.description}}
</a>
</template>
<span>{{item.file_name_original}}</span>
</v-tooltip>
<div v-else>
<a
v-if="item.gdoc"
style="text-decoration: none; color: orange;"
#click.prevent="gdocDialog = true;editingFile = item"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.file_name_original}}
</a>
<a
v-else
:href="'/getFile?id='+item.id"
style="text-decoration: none; color: orange;"
class="d-block text-truncate"
:style="$vuetify.breakpoint.mdAndUp ? 'max-width:'+aWidth+'px;' : 'max-width:'+bWidth+'px;'"
>
{{item.file_name_original}}
</a>
</div>
</v-col>
<v-col
cols="12"
md="1"
style="min-width: 30px; max-width: 30px;"
class="flex-grow-1 flex-shrink-0"
v-show="$vuetify.breakpoint.mdAndUp"
>
<v-edit-dialog
:return-value.sync="item.description"
#save="editFileInline()"
#open="inlineEditOpen(item, index)"
v-if="showActions"
>
<template v-slot:input>
<v-text-field
ref="inline_file"
v-model="editingFile.description"
label="Edit"
single-line
counter
></v-text-field>
</template>
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-icon
small
class="ml-2"
color="orange"
v-bind="attrs"
v-on="on"
width="100%"
>
mdi-pencil
</v-icon>
</template>
<span>Edit the file description</span>
</v-tooltip>
</v-edit-dialog>
</v-col>
</v-row>
</template>
<template v-slot:item.icon="{ item }">
<v-icon
:color="item.icon_color"
>
{{item.icon}}
</v-icon>
</template>
<template v-slot:item.uploaded="{ item }">
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<div v-bind="attrs"
v-on="on">
{{item.date_difference}}
</div>
</template>
<span>
<v-avatar
size="26px"
class="mr-2"
>
<img
:src="'/img/profile-pictures/'+item.user.profile_photo_thumb"
>
</v-avatar>
{{item.pretty_date}} by {{item.user.full_name}}</span>
</v-tooltip>
</template>
<template v-slot:item.actions="{item}" v-if="showActions">
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-icon
small
color="red"
#click="showDeleteDialog(item)"
v-bind="attrs"
v-on="on"
>
mdi-delete
</v-icon>
</template>
<span>Delete</span>
</v-tooltip>
</template>
</v-data-table>
<!-- Upload modal -->
<v-dialog
v-model="fileUploadDialog"
max-width="500px"
width="500px"
:transition="transitionSiteWide()"
persistent
v-if="showActions"
>
<v-card>
<v-progress-linear
indeterminate
color="yellow darken-2"
v-show="fileUploadProcess"
></v-progress-linear>
<v-toolbar
dark
class="primary"
dense
elevation="0"
>
<v-icon class="mr-2">mdi-cloud-upload</v-icon>
<v-toolbar-title class="text">Upload File(s)</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-card-text class="pt-3" v-show="!fileUploadGDoc">
<template>
<v-file-input
small-chips
:label="!oneFileOnly ? 'Upload multiple files by clicking here' : 'Click here to upload a file'"
type="file"
ref="files"
:accept="acceptedFiles()"
#change="onFilePicked()"
:key="componentKey"
show-size
counter
:multiple="!oneFileOnly"
:rules="!oneFileOnly ? rules : rulesSingle"
></v-file-input>
</template>
</v-card-text>
<v-card-text class="pt-3" v-show="fileUploadGDoc">
<v-text-field
ref="gdoc_description"
v-model="gdoc_description"
label="Description"
:rules="gdoc_description_rules"
prepend-icon="mdi-pencil"
></v-text-field>
<v-text-field
ref="gdoc_link"
v-model="gdoc_link"
label="Link to your Google Document"
:rules="gdoc"
prepend-icon="mdi-google-drive"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
text
#click="ok"
>
Close
</v-btn>
<v-btn
class="primary"
text
v-show="fileUploadButton"
#click="uploadFiles()"
:loading="fileUploadProcess"
>
Upload
</v-btn>
<v-btn
class="primary"
text
v-show="gdocValidated()"
#click="uploadFiles()"
:loading="fileUploadProcess"
>
Attach
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Delete dialog -->
<v-dialog
v-if="showActions"
v-model="deleteFileConfirm"
max-width="400px"
:transition="transitionSiteWide()"
>
<v-card elevation="0">
<v-progress-linear
indeterminate
color="yellow darken-2"
v-show="deleteFileLoading"
></v-progress-linear>
<v-toolbar
dark
class="primary"
dense
elevation="0"
>
<v-icon class="mr-2">mdi-text-box-minus</v-icon>
<v-toolbar-title class="text">{{editingFile.description ? editingFile.description : editingFile.file_name_original}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-card-text class="pb-0">
<v-container>
<p>Are you sure you want to delete this file?</p>
<p>{{editingFile.description ? editingFile.description : editingFile.file_name_original}}
will be removed from the system.</p>
</v-container>
</v-card-text>
<v-card-actions>
<v-btn
text
#click="deleteFileConfirm = false"
>
Close
</v-btn>
<v-spacer></v-spacer>
<v-btn
class="primary"
text
#click="deleteSet()"
>
Yes, delete this file
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Gdoc dialog -->
<v-dialog
v-model="gdocDialog"
max-width="1000px"
width="100%"
:transition="transitionSiteWide()"
>
<v-card elevation="0">
<v-toolbar
dark
color="teal"
dense
elevation="0"
>
<v-icon class="mr-2">mdi-google-drive</v-icon>
<v-toolbar-title class="text">{{editingFile.description ? editingFile.description : editingFile.file_name_original}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-card-text class="pa-0">
<iframe ref="gdocIframe" :src="editingFile.file_name_original" :style="'height:'+iframeHeight+'px;width:100%;border:0'"></iframe>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
text
#click="gdocDialog = false"
>
Close
</v-btn>
<v-btn
class="primary"
text
link
#click="openGdoc(editingFile.file_name_original);"
>
Open in new window
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
props: {
type: String,
id: [Number, String],
paginate: Number,
fileUploadDialog: Boolean,
fileUploadGDoc: Boolean,
ok: Function,
showActions: Boolean,
forceHeight: { type: Number, default: 0 },
oneFileOnly: Boolean,
passFiles: Boolean,
passedFiles: Array,
},
data () {
return {
headers: [
{
text: '',
align: 'start',
sortable: false,
value: 'icon',
width: '20px'
},
{ text: 'Description', value: 'description', sortable: false, },
{ text: 'Uploaded', value: 'uploaded', width: '150px' },
{ text: 'Actions', value: 'actions', sortable: false, width: '80px', align: 'center' },
],
headersRead: [
{
text: '',
align: 'start',
sortable: false,
value: 'icon',
width: '20px'
},
{ text: 'Description', value: 'description', sortable: false, },
{ text: 'Uploaded', value: 'uploaded', width: '150px' },
],
rules: [
files => !files || !files.some(file => file.size > 20_097_152) || 'Each file cannot exceed 20mb',
],
rulesSingle: [
],
gdoc: [
(value) => !!value || "Required",
(value) => this.isURL(value) || "URL is not valid",
],
gdoc_description_rules: [
(value) => !!value || "Required",
],
files: [],
fileUploadButton: false,
deleteFileConfirmLoading: false,
fileUpload: false,
fileUploadProcess: false,
componentKey: 0,
postFormData: new FormData(),
editingFile: {
description: null,
index: null,
file_name_original: null,
},
deleteFileConfirm : false,
deleteFileLoading: false,
gdoc_link: null,
gdoc_description: null,
gdocDialog: false,
iframeHeight: 0,
aWidth: 0,
loading: true,
}
},
computed:{
orderedFiles: function () {
return _.orderBy(this.files, 'created_at', 'desc')
},
},
watch: {
files: {
immediate: false,
handler(){
if(this.files){
this.total = this.files.length;
this.$emit('totalFiles', this.total);
}else{
this.total = 0;
}
},
},
id: {
immediate: false,
handler(){
this.getFiles()
},
}
},
methods: {
async getFiles(){
this.loading = true;
await axios
.get('/app/files/getFiles?id=' + this.id + '&type=' + this.type)
.then(response => {
if (response.data.length) {
this.files = response.data;
this.$emit('totalFiles', this.files.length);
this.resize();
this.loading = false;
} else {
this.files = [];
this.$emit('totalFiles', 0);
this.loading = false;
}
})
.catch(error => {
this.majorError();
})
.finally()
},
onFilePicked(e) {
this.postFormData = new FormData();
if(this.$refs.files.validate()){
this.fileUploadButton = true;
}
this.postFormData = new FormData();
for(let key in event.target.files){
if(key >= 0){
this.postFormData.append( 'files[]', event.target.files[key]);
}
}
},
async uploadFiles(){
this.fileUploadProcess = true;
this.postFormData.append('type', this.type);
this.postFormData.append('id', this.id);
this.postFormData.append('gdoc', this.fileUploadGDoc);
this.postFormData.append('gdoc_link', this.gdoc_link);
this.postFormData.append('gdoc_description', this.gdoc_description);
const res = await this.callApi('post', '/app/files/uploadFiles', this.postFormData);
if(res.status===200){
this.componentKey++; //reset trick
this.snackbar(res.data.msg,res.data.type);
this.ok();
this.fileUploadProcess = false;
this.gdoc_link = null;
this.gdoc_description = null;
this.$refs.gdoc_link.reset()
this.$refs.gdoc_description.reset()
if(res.data.files){
for (const file of res.data.files) {
this.files.push(file);
}
}
this.resize();
this.fileUploadButton = false;
}else{
this.fileUploadProcess = false;
this.snackbar(res.data.msg, res.data.type);
}
},
inlineEditOpen (item) {
let obj = { ...item, editingIndex: this.files.indexOf(item) }
this.editingFile = obj;
},
async editFileInline(){
const file = Object.assign({}, this.editingFile); //Turn array into object for laravel
const res = await this.callApi('post', '/app/files/updateFile',
{file: file});
if(res.status===201){
this.files[this.editingFile.editingIndex].description = this.editingFile.description;
this.snackbar(this.editingFile.description + " has been edited successfully", 'success');
this.resize();
}else{
if(res.status===422){
for(let i in res.data.errors) {
this.snackbar(res.data.errors[i][0], 'error');
}
}else{
this.snackbar("There has been an error, we don't have any more information for you", 'error');
}
}
},
showDeleteDialog(file){
this.deleteFileConfirm = true;
let obj = { ...file, index: this.files.indexOf(file)}
this.editingFile= obj;
},
async deleteSet(){
this.deleteFileLoading = true;
const res = await this.callApi('post', '/app/files/deleteFile', this.editingFile);
if(res.status===200){
this.files.splice(this.editingFile.index, 1);
this.snackbar("File deleted successfully", 'success');
this.deleteFileConfirm = false;
}else{
if(res.status===422){
this.snackbar(res.data.msg, 'error');
}
}
this.deleteFileLoading = false;
},
gdocValidated(){
if(this.gdoc_link&&this.$refs.gdoc_link.validate()&&this.gdoc_description){
return true;
}
},
openGdoc(url){
window.open(url, '_blank').focus();
},
onResize() {
this.iframeHeight = window.innerHeight - 220;
if(this.showActions){
this.aWidth = this.$refs.aWidth.$el.clientWidth - 355;
this.bWidth = this.$refs.aWidth.$el.clientWidth - 150;
}else{
this.aWidth = this.$refs.aWidth.$el.clientWidth - 270;
this.bWidth = this.$refs.aWidth.$el.clientWidth - 65;
}
},
resize(){
setTimeout(() => window.dispatchEvent(new Event('resize')), 1);
},
},
async mounted(){
if(this.passFiles){
this.files = this.passedFiles;
//console.log(this.passedFiles,this.files)
this.loading = false;
}else{
this.getFiles();
}
this.onResize();
this.resize();
},
created(){
console.log(this.passFiles,this.passedFiles); //this.passedFiles shows as undefined
window.addEventListener("resize", this.onResize);
},
destroyed(){
window.removeEventListener("resize", this.onResize);
this.editingFile = null;
},
}
</script>
What am I missing here?

How to use "Vue-infinite-loading" in nuxt without loading data by axios?

I am working on a Nuxt app. For one of my dynamic pages I want to show data with infinite scrolling. For that purpose I decided to use Vue-infinite-loading. I read this article and also skimmed the documentation of Vue-infinite-loading. In both of them they were using axios module to load data step by step to show in the page when the scroll reaches to specific point in that page. But in my page the data is already present in the page according to page-id with the help of $strapi module and Nuxt fetch hook. The whole code of my page is like below:
<template>
<v-container fluid>
<v-row>
<v-col cols="10" class="mx-auto">
<p v-if="$fetchState.pending">
در حال بارگذاری اطلاعات
</p>
<p v-else-if="$fetchState.error">
مشکلی در نمایش اطلاعات به وجود آمده است.
</p>
<div v-else>
<h1 v-html="this.fourType[this.nameRoute]['title']">
</h1>
<section>
<BaseCard
v-for="item in listShow"
:key="item.id"
:imgId = "item.id"
:sizeCard = "270"
>
<template v-slot:tozih>
{{ item.tozih }}
</template>
<template v-slot:aout>
{{ item.author }}
</template>
</BaseCard>
</section>
<infinite-loading
spinner="spiral"
#infinite="infiniteScroll"
>
</infinite-loading>
</div>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
data() {
return {
listBooks: [],
fourType:{
short: {
title: "در این قسمت می‌توانید کتابها را به ترتیب از کوچک (کمترین تعداد صفحه) به بزرگ (بیشترین تعداد صفحه) مشاهده نمایید:",
request: 1
},
programming: {
title: "در این قسمت می‌توانید کتب مرتبط با برنامه‌نویسی (دارای برچسب برنامه‌نویسی) را به ترتیب به روزرسانی (از جدیدترین به قدیمی‌ترین) مشاهده نمایید:",
request: 2
},
new: {
title: "در این قسمت می‌توانید کتب موجود در سایت را به ترتیب به روز رسانی (از جدید به قدیم) مشاهده نمایید:",
request: 3
},
random: {
title: "در این قسمت می‌توانید به صورت تصادفی به مشاهده تمامی کتب موجود در سایت بپردازید:",
request: 4
}
},
nameRoute: this.$route.params.type
}
}, // end of data
computed: {
listShow: function() {
return [ this.listBooks[0], this.listBooks[1] ]
}
}, // end of computed
methods: {
infiniteScroll($state) {
if (this.listBooks.length > 1) {
this.listBooks.forEach((item) => this.listShow.push(item))
$state.loaded()
}
else {
$state.complete()
}
}
}, // end of methods
async fetch() {
switch (this.nameRoute) {
case "short":
this.listBooks = await this.$strapi.$books.find("_sort=pageQuantity:ASC");
break;
case "programming":
this.listBooks = await this.$strapi.$books.find({ 'tags.name': ['برنامه نویسی'], _sort: 'updated_at:DESC' });
break;
case "new":
this.listBooks = await this.$strapi.$books.find("_sort=updated_at:DESC");
break;
default:
this.listBooks = this.$store.getters["books/randomBook"](this.$store.state.books.list2.length);
break;
}
}
}
</script>
<style scoped>
</style>
In the code I get the data in fetch hook (that is different according to page id) and then put it in listBooks Vue data. What I want to do is that show for example 2 data in listBooks first and when the scroll reached to the end of the second data (second card here), I show the rest of data step by step with infinite scrolling method. So I used a Computed data called listShow and used it in v-for. Then I put the code I thought that maybe it might work inside infiniteScroll method. But obviously that does not work, because I just guess that. If anyone know that how change that code to work and show data in infinite scrolling please help me to solve this issue.
Usually, an infinite loader is used to have a small subset of data that you then expand for performance reasons: not having to load 100 elements at once but 10, then 10 more etc...
If you already have the data locally at once and like the behavior of it, without any "pagination" needed from your Strapi backend, you can always watch for the #infinite event and increase the size of your initial array of elements with the next one.
Saying that if you display 10 of them, want to scroll down and show 10 more: display the first 10, then when the infinite event is triggered, append 10 more items to your initial array and so on.
My previous answer may help understand it a bit more.
PS: beware of some reactivity issues that you may face one day when dealing with arrays.
I finally with the help of kissu answer and with the inspiration of the code of the article mentioned in my question found the solution. Here I will post the code of my whole Nuxt page to show the answer for using other developers:
<template>
<v-container fluid>
<v-row>
<v-col cols="10" class="mx-auto">
<p v-if="$fetchState.pending">
در حال بارگذاری اطلاعات
</p>
<p v-else-if="$fetchState.error">
مشکلی در نمایش اطلاعات به وجود آمده است.
</p>
<div v-else>
<h1 v-html="this.fourType[this.nameRoute]['title']">
</h1>
<section>
<BaseCard
v-for="item in list2"
:key="item.id"
:imgId = "item.id"
:sizeCard = "270"
>
<template v-slot:tozih>
{{ item.tozih }}
</template>
<template v-slot:aout>
{{ item.author }}
</template>
</BaseCard>
</section>
<infinite-loading
spinner="spiral"
#infinite="infiniteScroll"
>
</infinite-loading>
</div>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
data() {
return {
listBooks: [],
page: 0,
list2: [],
fourType:{
short: {
title: "در این قسمت می‌توانید کتابها را به ترتیب از کوچک (کمترین تعداد صفحه) به بزرگ (بیشترین تعداد صفحه) مشاهده نمایید:",
request: 1
},
programming: {
title: "در این قسمت می‌توانید کتب مرتبط با برنامه‌نویسی (دارای برچسب برنامه‌نویسی) را به ترتیب به روزرسانی (از جدیدترین به قدیمی‌ترین) مشاهده نمایید:",
request: 2
},
new: {
title: "در این قسمت می‌توانید کتب موجود در سایت را به ترتیب به روز رسانی (از جدید به قدیم) مشاهده نمایید:",
request: 3
},
random: {
title: "در این قسمت می‌توانید به صورت تصادفی به مشاهده تمامی کتب موجود در سایت بپردازید:",
request: 4
}
},
nameRoute: this.$route.params.type
}
}, // end of data
methods: {
infiniteScroll($state) {
setTimeout(() => {
let emptyArr = [];
for (let index = this.page*10; index < this.page*10+10; index++) {
if (this.listBooks[index]) {
emptyArr.push(this.listBooks[index])
}
}
if (emptyArr.length > 0) {
emptyArr.forEach(
(item) => this.list2.push(item)
)
$state.loaded();
} else {
$state.complete()
}
this.page++
}, 500)
}
}, // end of methods
async fetch() {
switch (this.nameRoute) {
case "short":
this.listBooks = await this.$strapi.$books.find("_sort=pageQuantity:ASC");
break;
case "programming":
this.listBooks = await this.$strapi.$books.find({ 'tags.name': ['برنامه نویسی'], _sort: 'updated_at:DESC' });
break;
case "new":
this.listBooks = await this.$strapi.$books.find("_sort=updated_at:DESC");
break;
default:
this.listBooks = this.$store.getters["books/randomBook"](this.$store.state.books.list2.length);
break;
}
}
}
</script>
<style scoped>
</style>
the two important changes were:
1- Using an empty array called list2 in my Vue data instead of Vue computed data.
2- Using a variable called emptyArr in my infiniteScroll method that holds only for example 10 items of the original data (Vue listBooks data) and is showing them with scrolling the page each time that 10 data passed from the user view.

Vuejs sidebar menu with subitems

I am trying to create a sidebar menu dinamically with vuejs and vuenotify.
I have seen a sample on vuetify site using something as bellow.
The problem with this code, is that always shows the append-icon ">" on end of item title .
I´d like to show the append-icon ">", only if I have subittems.
I have created an item called "Manuutenção" with subtitems. No other has subitems.
Then, i´d like only "Manutençao" item or other(if I create with subitems") showing the ">".
Is possible fix it?
<v-list>
<v-list-group
v-for="item in items"
:key="item.title"
v-model="item.active"
:prepend-icon="item.icon"
no-action
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="subItem in item.items"
:key="subItem.title"
link
router
:to="subItem.to"
>
<v-list-item-content>
<v-list-item-title v-text="subItem.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
</v-list>
export default {
name: "DashboardCoreDrawer",
data: () => ({
items: [
{
icon: "mdi-view-dashboard",
title: "Dashboard",
to: "/",
},
{
icon: "mdi-account",
title: "Usuários",
to: "/usuarios",
},
{
title: "Clientes",
icon: "mdi-map-marker",
to: "/clientes",
},
{
title: "Manutenção",
icon: "mdi-clipboard-outline",
to: "",
items: [
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
to: "/planos",
},
],
},
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
to: "/planos",
},
{
title: "Tabelas",
icon: "mdi-chart-bubble",
to: "/tabelas",
},
{
title: "Atendimento",
icon: "mdi-bell",
to: "atendimentos",
},
],
}),
}
Additional information:
Is possible make adaptations to two or three submenu items. For sample:
items:[
{
title: "Manutenção",
icon: "mdi-clipboard-outline",
to: "",
items: [
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
to: "",
items: [
{
title: "Test1,
icon: "mdi-chart-bubble",
to: "/test1",
},
{
title: "Test2",
icon: "mdi-chart-bubble",
to: "/test2",
},
],
},
]
Icon at "Planos"
The main trick is to render v-list-group when item has subitems and v-list-item otherwise. See an example below:
<template>
<v-list>
<template v-for="item in items">
<v-list-group
:key="item.title"
v-if="item.items !== undefined"
v-model="item.active"
no-action
>
<template v-slot:activator>
<v-list-item-avatar left>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</template>
<v-list-item
v-for="subItem in item.items"
:key="subItem.title"
link
router
:to="subItem.to"
>
<v-list-item-avatar left>
<v-icon>{{ subItem.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="subItem.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-group>
<v-list-item v-else :key="item.title" link router :to="item.to">
<v-list-item-avatar left>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
</v-list>
</template>
Edited:
For a multilevel solution, basically, you have to create a component for list item and import it recursively into itself. Something like that:
mainlist.vue
<template>
<div>
<v-list>
<list-item v-for="item in items" :item="item" :key="item.title">
</list-item>
</v-list>
</div>
</template>
<script>
export default {
name: "DashboardCoreDrawer",
components: {
ListItem: () => import("./listitem.vue"),
},
data: () => ({
items: [
{
title: "Convênios",
icon: "mdi-clipboard-outline",
to: "/convenios",
},
{
title: "Planos",
icon: "mdi-format-font",
items: [
{
title: "Test1",
icon: "mdi-chart-bubble",
items: [
{
title: "Test4",
icon: "mdi-chart-bubble",
to: "/test1",
},
{
title: "Test5",
icon: "mdi-chart-bubble",
to: "/test2",
},
],
},
{
title: "Test2",
icon: "mdi-chart-bubble",
to: "/test2",
},
],
},
],
}),
};
</script>
listitem.vue
<template>
<div>
<v-list-group
v-if="item.items !== undefined"
v-model="item.active"
no-action
:sub-group="isSubGroup"
:class="isSubGroup ? 'right-icon' : ''"
>
<template v-slot:activator>
<v-list-item-content>
<v-list-item-title>
<v-icon>{{ item.icon }}</v-icon>{{ item.title }}
</v-list-item-title>
</v-list-item-content>
</template>
<template v-for="subItem in item.items">
<list-item :item="subItem" :is-sub-group="true" :key="subItem.title">
</list-item>
</template>
</v-list-group>
<v-list-item v-else link router :to="item.to">
<v-list-item-avatar left>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</div>
</template>
<script>
export default {
name: "listitem",
props: ["item", "isSubGroup"],
components: {
ListItem: () => import("./listitem.vue"),
},
};
</script>
<style>
.right-icon .v-list-group__header {
display: flex !important;
flex-direction: row-reverse !important;
}
</style>

Change the background color of a list item in Vuetify

I have a dynamically created list that I want to be able to add a click event to. Clicking on a list item should turn the background color green. Clicking it again would remove the green.
The problem I am having is turning a individual list item green and not the whole list. I have tried adding a index to each item, which works in turning the list item green, but doesn't stay green once I click another list item.
Here is the code I have.
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
selected: -1,
isLoading: true,
bool: true,
isActive: false,
items: [
{
icon: "print",
iconClass: "grey lighten-1 white--text",
title: "Printers",
subtitle: "MHC PDF Pro",
subtitle2: "HP LaserJet P4015 UPD PCL 6",
subtitle3: "RICOH Africo MP C3001"
},
{
icon: "email",
iconClass: "grey lighten-1 white--text",
title: "Email",
subtitle: "s.miller#mhc.com",
subtitle2: "hr#mhc.com",
subtitle3: "payroll#mhc.com"
},
{
icon: "mdi-fax",
iconClass: "grey lighten-1 white--text",
title: "Fax",
subtitle: "612-555-5555",
subtitle2: "952-555-5555",
subtitle3: "763-555-5555"
},
{
icon: "mdi-inbox-arrow-up",
iconClass: "grey lighten-1 white--text",
title: "File Transfer",
subtitle: "Text",
subtitle2: "CSV"
},
{
icon: "mdi-file-pdf",
iconClass: "grey lighten-1 white--text",
title: "PDF Creation"
}
]
}),
methods: {
changeColor() {
this.isLoading = !this.isLoading;
},
one() {
this.bool = !this.bool;
this.isActive = true
console.log('one');
},
two() {
this.bool = !this.bool;
this.isActive = false
console.log('two');
}
},
})
.active {
background: rgb(17, 128, 17);
}
.is-green {
background: #4caf50 !important;
}
.is-gray {
background: #505050 !important;
}
<html>
<head>
<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">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</head>
<body>
<div id="app">
<v-app>
<v-content>
<v-card>
<v-toolbar color="#1f497d" dark dense>
<v-toolbar-title>My processes</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<v-list two-line subheader>
<v-list-item v-for="(item ) in items" #click="bool ? one() : two()" v-bind:class="{ active: isActive}" :key="item.title">
<v-list-item-avatar>
<v-icon
dark
:class="{'is-gray': isLoading, 'is-green': !isLoading }"
v-text="item.icon"
></v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
<v-list-item-subtitle v-text="item.subtitle"></v-list-item-subtitle>
<v-list-item-subtitle v-text="item.subtitle2"></v-list-item-subtitle>
<v-list-item-subtitle v-text="item.subtitle3"></v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-btn #click="printWindow()">
Setup
<!-- <v-icon color="grey lighten-1">mdi-information</v-icon> -->
</v-btn>
</v-list-item-action>
</v-list-item>
<v-divider inset></v-divider>
<v-subheader inset>
<v-checkbox #change="changeColor()" label="Send all documents to processes above"></v-checkbox>
<v-spacer></v-spacer>
<v-btn color="success" class="mr-4" #click="printWindow()">OK</v-btn>
<v-btn color="error" class="mr-4" v-on:click="printerHidden = !printerHidden">Cancel</v-btn>
</v-subheader>
</v-list>
<v-subheader inset>
</v-subheader>
</v-card>
</v-content>
</v-app>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
</body>
</html>
You could use an array of activeItems and a toggle method like...
toggleActive(idx) {
let pos = this.activeItems.indexOf(idx)
pos === -1 ? this.activeItems.push(idx) : this.activeItems.splice(pos,1)
}
Then check if the item is in the array of activeItems...
<v-list two-line subheader>
<v-list-item v-for="(item,idx) in items"
#click="toggleActive(idx)"
:class="{active: activeItems.indexOf(idx)>-1}"
:key="item.title">...
</v-list-item>
</v-list>
Demo