Vue js Dropzone disable base64 - vue.js

I have this on data
dropzoneOptions: {
url: "https://httpbin.org/post",
thumbnailWidth: 150,
maxFilesize: 1,
headers: { "My-Awesome-Header": "header value" },
acceptedFiles: "image/*",
autoProcessQueue: false,
addRemoveLinks: true,
dictRemoveFile: 'Borrar',
},
on html:
<dropzone
id="myDropzone"
ref="myDropzone"
:options="dropzoneOptions"
v-on:vdropzone-file-added="fileAdded"
:destroyDropzone="true"
:useCustomSlot="true"
:style="`border-color: ${colorDrop}; color: ${colorDrop};`"
:multipleUpload="false"
>
<div class="dropzone-custom-content">
<v-icon> mdi-cloud-upload-outline </v-icon>
<h3 class="dropzone-custom-title">Click</h3>
</div>
</dropzone>
<div v-if="dropzoneError != null" class="caption red--text mb-3 pl-3" v-text="dropzoneError"></div>
<v-btn
color="grey"
dark
class="mt-2"
rounded
#click="add"
>
Add
<v-icon right ligth> mdi-camera </v-icon>
</v-btn>
And I get value:
let imagesDropzone = vm.$refs.myDropzone.getAcceptedFiles();
if (imagesDropzone.length == 0) {
vm.dropzoneError = "Add Image";
//console.log("Error", vm.dropzoneError);
return false;
} else {
vm.dropzoneError = null;
data.imagen = vm.$refs.myDropzone.getAcceptedFiles();
imagesDropzone.forEach(element => {
// data.imagen = element.dataURL;
});
//console.log("Success");
}
But I'm getting in format base64, how I can disable that format??
I only want to get the value of the image in its basic format, I don't want it to be in base64 format, it is possible to disable the base64 format
?

Related

Is there any way to pass props to component without calling it

I have 3 components like: Parent, First-child and Second-child. And I am iterating First-child in Parent component in array(it is cards), and I want to call Second-child in Parent component with First-child's props(props of one card).
My Parent component looks like this(how I am calling First-child):
``
<CardComponent
v-for="card of cards"
:key="card.urlsId"
:cardImages="card.images"
:cardTitle="card.title"
:cardDescription="card.description"
:mediaRef="card.urlsId"
:dbRef="card.dbId"
:deleteBtn="true"
:imagesWithSlider="true"
/>
And my First child is:
<template>
<div class="cards">
<v-card class="card-container">
<div class="delete-btn">
<v-btn
v-if="deleteBtn"
class="mx-2"
fab
dark
small
#click="$emit('onOpenDeleteModal')"
>
<v-icon dark> mdi-delete </v-icon>
</v-btn>
</div>
<ImageSlider
v-if="imagesWithSlider"
:imagesArray="cardImages"
:arrowBtns="false"
/>
<div class="text-container">
<h3 class="card-title">{{ cardTitle }}</h3>
<p class="card-description">{{ cardDescription }}</p>
</div>
</v-card>
</div>
</template>
<script>
export default {
props: {
cardImages: {
type: Array,
default: null,
},
cardTitle: {
type: String,
default: 'Title',
},
cardDescription: {
type: String,
default: 'Description',
},
deleteBtn: {
type: Boolean,
},
imagesWithSlider: {
type: Boolean,
},
mediaRef: {
type: String,
default: '',
},
dbRef: {
type: String,
default: '',
},
deleteModalOpen: {
type: Boolean,
},
},
emits: ['onOpenDeleteModal', 'onCloseDeleteModal'],
}
</script>
And my Second-child is:
<template>
<v-card class="modal" :loading="newCard.loading ? true : false">
<v-card class="modal-header">
<h3 v-if="addCardModal" class="header-title">New card</h3>
<h3 v-if="deleteModal" class="header-title">Delete</h3>
<v-icon aria-hidden="false" width="100%" #click="$emit('closeModal')"
>mdi-close</v-icon
>
</v-card>
<!-- Delete Modal -->
<div v-if="deleteModal" class="modal-delete">
<h3>Are you really want to delete this card ?</h3>
<div class="modal-delete-btns">
<v-btn #click="$emit('closeModal')">Cancel</v-btn>
<v-btn color="error" #click="$emit('onDeleteCard')">Delete</v-btn>
</div>
</div>
<!-- Add New Card Modal -->
<form
v-if="addCardModal"
class="modal-container"
#submit.prevent="postNewCardToDb"
>
<v-file-input
v-model="newCard.cardImages"
:clearable="false"
multiple
show-size
label="Upload card images"
#change="previewImage"
>
</v-file-input>
<v-file-input
v-model="newCard.cardVideo"
:clearable="false"
show-size
label="Upload video"
>
</v-file-input>
<div v-if="newCard.cardImageUrls.length !== 0" class="preview-image">
<ImageSlider :imagesArray="newCard.cardImageUrls" :arrowBtns="true" />
</div>
<v-text-field
v-model="newCard.cardTitle"
label="Enter card title"
></v-text-field>
<v-text-field
v-model="newCard.cardSnippet"
label="Enter card description"
></v-text-field>
<v-btn type="submit" :loading="newCard.loading ? true : false" block
>POST</v-btn
>
</form>
</v-card>
</template>
<script>
import { v4 as uuidV4, v1 as uuidV1 } from 'uuid'
export default {
/* eslint-disable no-console */
props: {
addCardModal: {
type: Boolean,
},
deleteModal: {
type: Boolean,
},
},
emits: ['closeModal', 'onDeleteCard'],
data() {
return {
newCard: {
loading: false,
cardImages: [],
cardVideo: null,
cardImageUrls: [],
cardTitle: '',
cardSnippet: '',
},
}
},
methods: {
previewImage($event) {
for (const image of event.target.files) {
this.newCard.cardImageUrls.push(URL.createObjectURL(image))
}
},
async getMediaUrlsFromStorage(newCardData) {
const cardMediaRef = uuidV1()
const cardImagesRef = await this.$fire.storage
.ref('/albums_cards/')
.child(cardMediaRef)
const videoRef = await cardImagesRef.child(uuidV4())
if (this.newCard.cardVideo) {
await videoRef.put(this.newCard.cardVideo)
const videoUrl = await videoRef.getDownloadURL()
newCardData.video = videoUrl
}
newCardData.urlsId = cardMediaRef
const promiseArr = this.newCard.cardImages.map(async (image) => {
const imageRef = cardImagesRef.child(uuidV4())
await imageRef.put(image)
const imageUrl = await imageRef.getDownloadURL()
newCardData.images.push(imageUrl)
})
await Promise.all(promiseArr)
},
async postNewCardToDb() {
this.newCard.loading = true
const newCardData = {
urlsId: '',
title: this.newCard.cardTitle,
description: this.newCard.cardSnippet,
video: '',
images: [],
}
await this.getMediaUrlsFromStorage(newCardData)
await this.$fire.database.ref('albums/cards').push(newCardData)
console.log(newCardData)
this.newCard.loading = false
this.newCard.cardTitle = null
this.newCard.cardSnippet = null
this.newCard.cardImages = []
this.newCard.cardImageUrls = []
this.newCard.cardVideo = null
},
},
}
</script>
First-child is a card component and I need to pass props of each card to Second-child without calling it. I cant call Second-child in First-child because of iteration.
I hope I expleined it well

Vuetify V-Data-Table search not working when :items refer to computed property

I have an app with a v-data-table where the :items are reffering to a computed property array. I dont know why, but the search function doesnt work and it makes me furious for hours.
I am not sure what I am doing wrong, I even added the item-key property and the items id to it which I usually never did in the past but it still doesnt work. Can anyone give me a hint?
<v-data-table
:search="search"
:loading="loading"
class="transparent"
dense
fixed-header
height="800"
:items="filteredItems"
:headers="headers"
item-key="id"
>
filteredItems() {
let items = this.items;
items = items.filter(
item =>
Number(item.attributes.token0_usd_value) >=
Number(this.showMinUSDValue)
);
items = items.filter(
item => item.attributes.new_position == this.newPositions
);
if (this.wallet_group !== "ALL") {
items = items.filter(
item => item.attributes.group == this.wallet_group
);
}
if (this.tradeSide !== "ALL") {
items = items.filter(
item => item.attributes.side.toUpperCase() == this.tradeSide
);
}
if (this.time_filter !== "ALL") {
const timestamp = this.getTimestampForInterval(this.time_filter);
items = items.filter(item => {
return (
new Date(item.attributes.block_timestamp).getTime() > timestamp
);
});
}
if (this.excludedSymbols.length > 0) {
items = items.filter(item => {
return (
!this.excludedSymbols.includes(
item.attributes.token0.attributes.symbol
) &&
!this.excludedSymbols.includes(
item.attributes.token1.attributes.symbol
)
);
});
}
return items;
}
},
<template>
<keep-alive>
<v-card class="transparent">
<v-card-title class="text-uppercase primary--text">
Dex Trades
</v-card-title>
<v-card-subtitle>
follow realtime dex trades for address pool
<v-row class="mt-6">
<v-col cols="12" md="2" lg="1">
<v-text-field
outlined
dense
label="$USD min"
v-model="showMinUSDValue"
></v-text-field>
</v-col>
<v-col cols="12" md="1">
<v-select
dense
outlined
:items="time_filters"
v-model="time_filter"
label="time"
></v-select
></v-col>
<v-col cols="12" md="1">
<v-select
dense
outlined
:items="wallet_groups"
v-model="wallet_group"
label="group"
></v-select
></v-col>
<v-col cols="12" md="1">
<v-select
dense
outlined
:items="tradeSideItems"
v-model="tradeSide"
label="side"
></v-select
></v-col>
<v-col cols="12" md="1">
<v-checkbox
dense
label="only new"
v-model="newPositions"
></v-checkbox>
</v-col>
<v-col cols="12" md="1">
<v-checkbox
dense
label="notifications"
v-model="playAudio"
></v-checkbox>
</v-col>
</v-row>
<v-row class="mt-0 pa-0">
<v-col cols="12" md="4">
<v-autocomplete
small-chips
dense
chips
multiple
:items="symbols"
v-model="excludedSymbols"
label="exclude symbols"
></v-autocomplete>
</v-col>
<v-col cols="12" md="2">
<v-text-field
dense
name="search"
label="search symbol"
v-model="search"
></v-text-field>
</v-col>
</v-row>
</v-card-subtitle>
<v-card-text>
{{filteredItems.length}}
<v-data-table
disable-pagination
:loading="loading"
class="transparent"
dense
fixed-header
height="800"
:items="filteredItems"
:headers="headers"
item-key="id"
>
<template v-slot:item.logo="{ item }">
<v-img
v-if="item.attributes.side == 'sell'"
contain
width="25"
height="25"
style="border-radius:50%"
:src="item.attributes.token0_ticker.attributes.image"
:lazy-src="item.attributes.token0_ticker.attributes.image"
></v-img>
<v-img
v-else
contain
width="25"
height="25"
style="border-radius:50%"
:src="item.attributes.token1_ticker.attributes.image"
:lazy-src="item.attributes.token1_ticker.attributes.image"
></v-img>
</template>
<template v-slot:item.attributes.side="{ item }">
<div
v-if="item.attributes.side === 'sell'"
class="error--text text-uppercase"
>
{{ item.attributes.side }}
</div>
<div v-else class="success--text text-uppercase">
{{ item.attributes.side }}
</div>
</template>
<template v-slot:item.ticker="{ item }">
<div>
<div v-if="item.attributes.side == 'sell'">
{{ item.attributes.token0.attributes.symbol }}
</div>
<div v-else>{{ item.attributes.token1.attributes.symbol }}</div>
</div>
</template>
<template v-slot:item.attributes.token0_usd_value="{ item }">
{{ getFormattedPrice(item.attributes.token0_usd_value) }}
</template>
<template v-slot:item.flow="{ item }">
<div v-html="getFlow(item)"></div>
</template>
<template v-slot:item.newPosition="{ item }">
<v-icon v-if="item.attributes.new_position == true" color="success"
>mdi-check-bold</v-icon
>
<v-icon v-else color="error">mdi-close-thick</v-icon>
</template>
<template v-slot:item.current="{ item }">
{{ getCurrentBalance(item) }}
</template>
<template v-slot:item.holdingsDifference="{ item }">
{{ getTokenHoldingsChange(item) }}
</template>
<template v-slot:item.address="{ item }">
<a
target="blank"
:href="getEtherscanURL('address', item.attributes.address)"
>
<div>
{{
item.attributes.address.substr(
item.attributes.address.length - 5
)
}}
</div></a
>
</template>
<template v-slot:item.prev="{ item }">
{{ getPreviousBalance(item) }}
</template>
<template v-slot:item.attributes.block_timestamp="{ item }">
{{ convertTimezone(item) }}
</template>
<template v-slot:item.actions="{ item }">
<v-btn
class="mr-2"
small
rounded
:href="getEtherscanURL('tx', item.attributes.transaction_hash)"
target="_blank"
color="warning"
>tx</v-btn
>
</template>
</v-data-table>
</v-card-text>
</v-card>
</keep-alive>
</template>
<script>
import Moralis from "moralis";
import syncAPI from "../../api/sync/sync.api";
const sound = require("#/assets/notification.mp3");
export default {
name: "Realtime-Table",
data: () => ({
search: "",
items: [],
newPositions: false,
loading: false,
showMinUSDValue: 25000,
notification: null,
playAudio: true,
wallet_group: "ALL",
tradeSide: "ALL",
tradeSideItems: ["ALL", "BUY", "SELL"],
wallet_groups: [],
excludedSymbols: [],
time_filter: "ALL",
time_filters: ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w", "ALL"],
headers: [
{
text: "ticker",
align: "center",
value: "ticker",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "buy/sell",
align: "start",
value: "attributes.side",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "flow",
align: "center",
value: "flow",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "dex",
align: "start",
value: "attributes.dex.attributes.identifier",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "$amount",
align: "start",
value: "attributes.token0_usd_value",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "$prev",
align: "start",
value: "prev",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "$current",
align: "start",
value: "current",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "address",
align: "start",
value: "address",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "new position",
align: "center",
value: "newPosition",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "timestamp",
align: "start",
value: "attributes.block_timestamp",
class: "background primary--text text-uppercase font-weight-black"
},
{
text: "actions",
align: "center",
value: "actions",
class: "background primary--text text-uppercase font-weight-black"
}
]
}),
computed: {
symbols() {
const unique = [
...new Set(
this.items.map(item => item.attributes.token0.attributes.symbol)
)
];
return unique;
},
filteredItems() {
let items = this.items;
items = items.filter(item => {
return (
item.attributes.token1.attributes.symbol.includes(
this.search.toUpperCase()
) ||
item.attributes.token0.attributes.symbol.includes(
this.search.toUpperCase()
)
);
});
items = items.filter(
item =>
Number(item.attributes.token0_usd_value) >=
Number(this.showMinUSDValue)
);
items = items.filter(
item => item.attributes.new_position == this.newPositions
);
if (this.wallet_group !== "ALL") {
items = items.filter(
item => item.attributes.group == this.wallet_group
);
}
if (this.tradeSide !== "ALL") {
items = items.filter(
item => item.attributes.side.toUpperCase() == this.tradeSide
);
}
if (this.time_filter !== "ALL") {
const timestamp = this.getTimestampForInterval(this.time_filter);
items = items.filter(item => {
return (
new Date(item.attributes.block_timestamp).getTime() > timestamp
);
});
}
if (this.excludedSymbols.length > 0) {
items = items.filter(item => {
return (
!this.excludedSymbols.includes(
item.attributes.token0.attributes.symbol
) &&
!this.excludedSymbols.includes(
item.attributes.token1.attributes.symbol
)
);
});
}
return items;
}
},
methods: {
getTimestampForInterval() {
switch (this.time_filter) {
case "5m":
return Date.now() - 5 * 1000 * 60;
case "10m":
return Date.now() - 10 * 1000 * 60;
case "15m":
return Date.now() - 15 * 1000 * 60;
case "30m":
return Date.now() - 30 * 1000 * 60;
case "1h":
return Date.now() - 1000 * 60 * 60;
case "4h":
return Date.now() - 1000 * 60 * 60 * 4;
case "8h":
return Date.now() - 1000 * 60 * 60 * 8;
case "12h":
return Date.now() - 1000 * 60 * 60 * 12;
case "1d":
return Date.now() - 1000 * 60 * 60 * 24;
case "1w":
return Date.now() - 1000 * 60 * 60 * 24 * 7;
case "1m":
return Date.now() - 1000 * 60 * 60 * 24 * 30;
default:
return Date.now() - 1000 * 60 * 5;
}
},
getTokenHoldingsChange(item) {
if (item.side == "buy") {
return Number(
item.attributes.token1_current_balance.amount_decimal -
item.attributes.token1_previous_balance.amount_decimal
).toFixed(5);
}
return Number(
item.attributes.token0_current_balance.amount_decimal -
item.attributes.token0_previous_balance.amount_decimal
).toFixed(2);
},
getCurrentBalance(item) {
if (item.attributes.side == "buy") {
return this.getFormattedPrice(
item.attributes.token1_current_balance.amount_decimal *
item.attributes.token1_current_balance.price.usdPrice
);
}
return this.getFormattedPrice(
item.attributes.token0_current_balance.amount_decimal *
item.attributes.token0_current_balance.price.usdPrice
);
},
getPreviousBalance(item) {
if (item.attributes.side == "buy") {
return this.getFormattedPrice(
item.attributes.token1_previous_balance.amount_decimal *
item.attributes.token1_previous_balance.price.usdPrice
);
}
return this.getFormattedPrice(
item.attributes.token0_previous_balance.amount_decimal *
item.attributes.token0_previous_balance.price.usdPrice
);
},
getFlow(item) {
const side = item.attributes.side;
if (side.toUpperCase() == "SELL") {
return `<div class="d-flex justify-center"><div class="error--text">${item.attributes.token0.attributes.symbol}</div> / <div class="success--text">${item.attributes.token1.attributes.symbol}</div></div>`;
}
return `<div class="d-flex justify-center"><div class="success--text">${item.attributes.token1.attributes.symbol}</div> / <div class="error--text">${item.attributes.token0.attributes.symbol}</div></div>`;
},
getFormattedPrice(number) {
return number.toLocaleString("en-US", {
style: "currency",
currency: "USD"
});
},
getEtherscanURL(type, value) {
if (type === "tx") return `https://etherscan.io/tx/${value}`;
return `https://etherscan.io/address/${value}`;
},
convertTimezone(item) {
if (item.attributes.block_timestamp)
return item.attributes.block_timestamp.toLocaleString("en-US", {
timeZone: "EST"
});
return "";
},
async subscribeToTransactions() {
try {
let query = new Moralis.Query("DexTrade");
query.include("dex");
query.include("token0");
query.include("token1");
query.include("token0_ticker");
query.include("token1_ticker");
let subscription = await query.subscribe();
subscription.on("open", () => {
console.log("DexTrades subscription opened");
});
subscription.on("create", object => {
console.log("obj before check", object.attributes);
if (
object.attributes.token0_current_balance.price &&
object.attributes.token0_previous_balance.price &&
object.attributes.token1_current_balance.price &&
object.attributes.token1_previous_balance.price
) {
this.items.unshift(object);
if (this.playAudio) {
this.notification.play();
}
}
});
} catch (error) {
alert(error);
}
},
async loadPreviousTransactions() {
const query = new Moralis.Query("DexTrade");
this.loading = true;
query.include("dex");
query.include("token0");
query.include("token1");
query.include("token0_ticker");
query.include("token1_ticker");
query.limit(500);
query.descending("createdAt");
this.items = await query.find();
this.loading = false;
}
},
async created() {
this.notification = new Audio(sound);
this.wallet_groups = await syncAPI.walletGroups();
await this.loadPreviousTransactions();
await this.subscribeToTransactions();
}
};
</script>
<style>
.element .v-data-table__wrapper::-webkit-scrollbar {
width: 24px;
height: 8px;
background-color: #143861;
}
.v-data-table > .v-data-table__wrapper > table > tbody > tr > td {
font-size: 16px;
}
</style>

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?

Can't add Object to an Array

I'm having an empty array for my breadcrumb and wanna fill it with every step i take in my category tree.
But as i said in Title, it won't add or to be more precise, it add but won't show!!
it wont show on my template! it wont show on console! only when i console.log("bread", Array.from(this.breadcrumbs)) it shows in console.
how can i fill my this.breadcrumbs with the category obj that i send trough an event!!
my category structure is like this: {id: 1, title: "Cat1"}
here is the code of page:
my main problem is that i'm checking if the breadcrumbs has length show it, but since i get empty arr even after push, my breadcrumb section just show the all.
Update:
i removed the if statements in my template, breadcrumb section and i when i click a cat it's title will be shown on breadcrumb but length and array still empty even as trying to display on template!! ( {{breadcrumb}} and {{breadcrumb.length}} are []and0` ).
BTW i'm on Nuxtjs 2.13
<template>
<div class="ma-4">
<!-- Breadcrumb -->
<div class="d-flex">
<template v-if="breadcrumbs.length">
<span role="button" #click.prevent="getAll()">{{lang.all}} > </span>
<span role="button" v-for="(br, index) in newBreadCrumb" :key="br.id" #click.prevent="goBread(br, index)">{{br.title}} > </span>
<span>{{breadcrumbs[breadcrumbs.length - 1]}}</span>
</template>
<template v-else>
<span>{{lang.all}}</span>
</template>
</div>
<!--btn add-->
<addcatbtn v-if="showBtn" #clicked="addNewCat()" />
<!--cards-->
<categorylistcard v-for="(category, index) in cmsCat"
:key="category.id"
:cat="category"
:index="index"
#addnewsub="addNewCat()"
/>
<!-- dialog -->
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline" v-if="editId">{{lang.edit}} </span>
<span class="headline" v-else>{{lang.addcat}} </span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field :label="lang.title" outlined v-model="title"></v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn class="text_main_color theme__cta__color px-5" #click="closeDialog()">{{lang.close}}</v-btn>
<v-btn class="text_main_color theme__btn__s px-5" #click="insertData()">{{lang.save}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<style>
</style>
<script>
import addcatbtn from '~/components/global/cms/addcatbtn'
const categorylistcard = ()=>import('~/components/' + process.env.SITE_DIRECTION + '/cms/categorylistcard')
export default {
layout:'cms',
components:{
'categorylistcard': categorylistcard,
'addcatbtn': addcatbtn
},
data(){
return{
dialog: false,
editId: 0,
newId: 0,
title: null,
catList: [],
editIndex: 0,
parentId: 0,
editSubCat: false,
showBtn: true,
onSubCat: false,
breadcrumbs: []
}
},
methods:{
addNewCat(){
this.dialog = true
this.title = null
},
closeDialog(){
this.dialog = false
this.title = null
this.editId = 0
this.editIndex = 0
},
getAll(){
this.$fetch()
this.showBtn = true
this.onSubCat = false
this.breadcrumbs = []
},
goBread(cat, index){
this.breadcrumbs.lenght = index
$nuxt.$emit('gobread',cat)
},
async insertData(){
if(this.notEmpty(this.title)){
if(this.editId){
let response = await this.axiosGet(`category/update/${this.editId}/${this.title}`)
if(this.resOk(response.status)){
const data = {index: this.editIndex , title: this.title}
if(this.editSubCat){
this.updateCmsSubCat(data)
}else{
this.updateCmsCat(data)
}
$nuxt.$emit('editedcat', {id: this.editId, title: this.title})
this.closeDialog()
}
}else{
if(this.onSubCat){
let response = await this.axiosGet(`category/insertsub/${this.newId}/${this.title}`)
if(this.resOk(response.status)){
// TODO: must get the id from response!!
console.log('insertsub')
const data = {id: this.newId*2 , title: this.title}
this.addCmsSubCat(data)
this.closeDialog()
}
}else{
let response = await this.axiosGet(`category/insert/${this.title}`)
if(this.resOk(response.status)){
// TODO: must get the id from response!!
const data = {id: 34 , title: this.title}
this.addCmsCat(data)
this.closeDialog()
}
}
}
}
}
},
async fetch(){
let response = await this.axiosGet(`categories/admin/0/1`)
if(this.notEmpty(response.data)){
this.setCmsCat(response.data.items)
}
},
computed:{
newBreadCrumb(){
let x = this.breadcrumbs
return x.splice(this.breadcrumbs.lenght-1,1)
}
},
created(){
this.$nuxt.$on('deletecat', (index)=>{
this.removeCmsCat(index)
})
this.$nuxt.$on('editcat', (category, index)=>{
this.title = category.title
this.editId = category.id
this.editIndex = index
this.dialog = true
})
this.$nuxt.$on('setparid', (id)=>{
this.parentId = id
})
this.$nuxt.$on('editsub', ()=>{
this.editSubCat = true
})
this.$nuxt.$on('showsub', (cat)=>{
this.newId = cat.id
this.showBtn = !this.showBtn
this.onSubCat = !this.onSubCat
})
this.$nuxt.$on('addbreadcrumb', (category)=>{
this.breadcrumbs.push(category) // category:{id: 1, title: "Cat1"}
console.log('cat: ')
console.log(category) // get the obj
console.log('bread: ')
console.log(this.breadcrumbs) // get empty array
})
}
}
</script>
In your computed, you are calling splice. That mutates the object. A big no-no in vue is to mutate your state from a computed property.
Try to create a new object and return it, by calling slice first:
const copy = x.slice()
copy.splice(this.breadcrumbs.length-1,1)
return copy
Also, you have a typo lenght instead of length

vuetify autocomplete allow unknown items between chips

I am trying to modify the sample code at https://vuetifyjs.com/en/components/autocompletes#example-scoped-slots to allow arbitrary content not matching any autocomplete items in between chips (so user can tag other users in a message similar to slack and facebook)
So for example, the user could type "Sandra" and then select "sandra adams", then type "foo" and then type another space and start typing "John" and the autcomplete would pop up again and allow the user to select "John Smith".
I've been through all the properties in the docs and there doesn't seem to be support for this built in.
I tried using custom filtering to ignore the irrelevant parts of the message when displaying autocomplete options, but the autocomplete seems to remove non-chip content when it loses focus and I can't see a property that allows me to prevent this behavior.
not sure if the autcomplete is the thing to be using or if I would be better off hacking combo box to meet this requirement, because this sample seems closer to what I'm tryng to do https://vuetifyjs.com/en/components/combobox#example-no-data, but then I believe I lose the ajax capabilities that come with automcomplete.
You can achieve this by combining the async search of the autocomplete with the combobox.
For example:
new Vue({
el: '#app',
data: () => ({
activator: null,
attach: null,
colors: ['green', 'purple', 'indigo', 'cyan', 'teal', 'orange'],
editing: null,
descriptionLimit: 60,
index: -1,
nonce: 1,
menu: false,
count: 0,
model: [],
x: 0,
search: null,
entries: [],
y: 0
}),
computed: {
fields () {
if (!this.model) return []
return Object.keys(this.model).map(key => {
return {
key,
value: this.model[key] || 'n/a'
}
})
},
items () {
return this.entries.map(entry => {
const Description = entry.Description.length > this.descriptionLimit
? entry.Description.slice(0, this.descriptionLimit) + '...'
: entry.Description
return Object.assign({}, entry, { Description })
})
}
},
watch: {
search (val, prev) {
// Lazily load input items
axios.get('https://api.publicapis.org/entries')
.then(res => {
console.log(res.data)
const { count, entries } = res.data
this.count = count
this.entries = entries
})
.catch(err => {
console.log(err)
})
.finally(() => (this.isLoading = false))
/*if (val.length === prev.length) return
this.model = val.map(v => {
if (typeof v === 'string') {
v = {
text: v,
color: this.colors[this.nonce - 1]
}
this.items.push(v)
this.nonce++
}
return v
})*/
},
model (val, prev) {
if (val.length === prev.length) return
this.model = val.map(v => {
if (typeof v === 'string') {
v = {
Description: v
}
this.items.push(v)
this.nonce++
}
return v
})
}
},
methods: {
edit (index, item) {
if (!this.editing) {
this.editing = item
this.index = index
} else {
this.editing = null
this.index = -1
}
},
filter (item, queryText, itemText) {
const hasValue = val => val != null ? val : ''
const text = hasValue(itemText)
const query = hasValue(queryText)
return text.toString()
.toLowerCase()
.indexOf(query.toString().toLowerCase()) > -1
}
}
})
<link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" integrity="sha256-mpnrJ5DpEZZkwkE1ZgkEQQJW/46CSEh/STrZKOB/qoM=" crossorigin="anonymous"></script>
<div id="app">
<v-app>
<v-content>
<v-container>
<v-combobox
v-model="model"
:filter="filter"
:hide-no-data="!search"
:items="items"
:search-input.sync="search"
hide-selected
label="Search for an option"
:allow-overflow="false"
multiple
small-chips
solo
hide-selected
return-object
item-text="Description"
item-value="API"
:menu-props="{ closeOnClick: false, closeOnContentClick: false, openOnClick: false, maxHeight: 200 }"
dark
>
<template slot="no-data">
<v-list-tile>
<span class="subheading">Create</span>
<v-chip
label
small
>
{{ search }}
</v-chip>
</v-list-tile>
</template>
<template
v-if="item === Object(item)"
slot="selection"
slot-scope="{ item, parent, selected }"
>
<v-chip
:selected="selected"
label
small
>
<span class="pr-2">
{{ item.Description }}
</span>
<v-icon
small
#click="parent.selectItem(item)"
>close</v-icon>
</v-chip>
</template>
<template
slot="item"
slot-scope="{ index, item, parent }"
>
<v-list-tile-content>
<v-text-field
v-if="editing === item.Description"
v-model="editing"
autofocus
flat
hide-details
solo
#keyup.enter="edit(index, item)"
></v-text-field>
<v-chip
v-else
dark
label
small
>
{{ item.Description }}
</v-chip>
</v-list-tile-content>
</template>
</v-combobox>
</v-container>
</v-content>
</v-app>
</div>
so I ended up building a renderless component that is compatible with vuetify as it goes through the default slot and finds any of the types of tags (textarea, input with type of text, or contenteditable) that tribute supports, and allows you to put arbitrary vue that will be used to build the tribute menu items via a scoped slot.
in future might try to wrap it as a small NPM package to anyone who wants a declarative way to leverage tribute.js for vue in a more flexible way than vue-tribute allows, but for now here's my proof of concept
InputWithMentions.vue
<script>
import Tribute from "tributejs"
// eslint-disable-next-line
import * as css from "tributejs/dist/tribute.css"
import Vue from "vue"
export default {
mounted() {
let menuItemSlot = this.$scopedSlots.default
let tribute = new Tribute({
menuItemTemplate: item =>
{
let menuItemComponent =
new Vue({
render: function (createElement) {
return createElement('div', menuItemSlot({ menuItem: item }))
}
})
menuItemComponent.$mount()
return menuItemComponent.$el.outerHTML
},
values: [
{key: 'Phil Heartman', value: 'pheartman'},
{key: 'Gordon Ramsey', value: 'gramsey'}
]})
tribute.attach(this.$slots.default[0].elm.querySelectorAll('textarea, input[type=text], [contenteditable]'))
},
render(createElement) {
return createElement('div', this.$slots.default)
}
}
</script>
User.vue
<InputWithMentions>
<v-textarea
box
label="Label"
auto-grow
value="The Woodman set to work at once, and so sharp was his axe that the tree was soon chopped nearly through.">
</v-textarea>
<template slot-scope="{ menuItem }">
<v-avatar size="20" color="grey lighten-4">
<img src="https://vuetifyjs.com/apple-touch-icon-180x180.png" alt="avatar">
</v-avatar>
{{ menuItem.string }}
</template>
</InputWithMentions>