Is there a way to dynamically change data names based on other data names? Here is my example.
Say I have these data elements.
export default {
data: function() {
return {
speed: "Standard",
speed_enabled: false,
flex_enabled: false,
};
},
While the user interacts with the front end, I pull in additional speed data, in the form of different names. ex. Priority, Expedited etc.
When I do this I want to change the names of the other data elements. Something like this:
export default {
data: function() {
return {
speed: "Standard",
{{this.speed}}_speed_enabled: false,
flex_enabled: false,
};
},
Something along those lines.. so speed_enabled becomes expedited_speed_enabled and priority_speed_enabled etc. etc.
Is this possible?
EDIT: here is all my code.. maybe someone can figure out how to pass different data to the same component.
Here is my parent component speeds.vue:
<template>
<v-app>
<v-container fill-height>
<v-layout row wrap align-center>
<v-flex xs8 class="mx-auto">
<h1 class="display-1 mont bold fix-title-height pb-3">FBA Shipping Settings</h1>
<v-tabs icons-and-text centered color="purple darken-3" dark class="elevation-12">
<v-tabs-slider color="green lighten-1"></v-tabs-slider>
<v-tab :key="speed" #click="setStandard">
Standard
</v-tab>
<v-tab :key="speed" #click="setExpedited">
Expedited
</v-tab>
<v-tab :key="speed" #click="setPriority">
Priority
</v-tab>
<v-tab-item :key="standard" >
<speed_details></speed_details>
</v-tab-item>
<v-tab-item :key="expedited">
<speed_details></speed_details>
</v-tab-item>
<v-tab-item :key="priority">
<speed_details></speed_details>
</v-tab-item>
</v-tabs>
</v-flex>
</v-layout>
</v-container>
</v-app>
</template>
<script>
import speed_details from '../components/speed_details.vue';
import {dataShare} from '../packs/application.js';
import axios from 'axios';
export default {
data: function() {
return {
speed: "Standard"
};
},
components: {
speed_details
},
created() {
console.log("this should get hit at the beginning and what's speed? " + this.speed);
dataShare.$emit('speed', this.speed);
},
methods: {
getSpeedInfo() {
axios.get('https://bc-ship-trimakas.c9users.io/return_speed_info', {params: {speed: this.speed}}).then(response => {
const speed_info = {
speed_enabled: true,
fixed_rate_amount: response.data.fixed_amount,
// this.fixed_enabled = response.data.fixed;
// this.flex_enabled = response.data.flex;
// this.flex_above_or_below_amount = response.data.flex_amount;
// this.percent_or_dollar = response.data.flex_dollar_or_percent;
free_enabled: response.data.free,
free_shipping_amount: response.data.free_shipping_amount
};
dataShare.$emit('speed_info', speed_info);
});
},
setStandard() {
this.speed = "Standard";
dataShare.$emit('speed', this.speed);
},
setExpedited() {
this.speed = "Expedited";
dataShare.$emit('speed', this.speed);
},
setPriority() {
this.speed = "Priority";
dataShare.$emit('speed', this.speed);
},
}
};
</script>
<style>
</style>
And then my child component speed_details.vue that is the same across all three tabs:
<template>
<v-card flat class="lightblue" :resize="moveIcon">
<v-form ref="form" v-model="valid" lazy-validation>
<v-layout row wrap>
<v-flex xs7>
<v-switch
:label="'Enable ' + speed + ' Shipping'"
:v-model="speed_enabled"
color="purple darken-3"
#change="enableAndDisable"
></v-switch>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs7>
<v-switch
label="Enable Flex Rate Shipping?"
:disabled="flex_switch_disabled"
v-model="flex_enabled"
color="purple darken-3"
#change="killFixed, removeFlexAmount"
></v-switch>
</v-flex>
<v-flex>
<v-tooltip top>
<v-icon slot="activator" color="purple darken-3" class="fix_icon">info</v-icon>
<span class="body-2">You can enable fixed or flex rate shipping but not both</span>
</v-tooltip>
</v-flex>
</v-layout>
<v-layout row wrap class="move-up">
<v-flex xs1>
<v-select
:disabled="!flex_enabled"
append-icon=""
color="purple darken-3"
:items="fee_type"
v-model="percent_or_dollar"
label="$ or %"
single-line
auto
hide-details
></v-select>
</v-flex>
<v-flex xs8>
<v-text-field
:disabled="!flex_enabled"
type="number"
color="purple darken-3"
label="Shipping Amount Above or Below FBA Fees"
v-model="flex_above_or_below_amount"
class="push-right"
></v-text-field>
</v-flex>
<v-flex xs1 v-bind="icon_adjust" class="push_down">
<v-tooltip top>
<v-icon slot="activator" color="purple darken-3" class="fix_icon">info</v-icon>
<span class="body-2">This amount will either increase or decrease(-) the fulfillment amount from Amazon.
</span>
</v-tooltip>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs7>
<v-switch
label="Enable Fixed Rate Shipping?"
:disabled="fixed_switch_disabled"
v-model="fixed_enabled"
color="purple darken-3"
#change="killFlex, removeFixedAmount"
></v-switch>
</v-flex>
<v-flex xs1>
<v-tooltip top>
<v-icon slot="activator" color="purple darken-3" class="fix_icon_less">info</v-icon>
<span class="body-2">This amount will be displayed to your customer and Amazon fees will be ignored.
</span>
</v-tooltip>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs4>
<v-text-field
:disabled="!fixed_enabled"
type="number"
color="purple darken-3"
label="Fixed Rate Amount"
v-model="fixed_rate_amount"
prefix="$"
class="move-up"
></v-text-field>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs7>
<v-switch label="Enable Free Shipping?" :disabled="!speed_enabled" v-model="free_enabled" color="purple darken-3"></v-switch>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs8>
<v-text-field
:disabled="!free_enabled"
type="number"
color="purple darken-3"
label="Order Amount to Qualify for Free Shipping"
v-model="free_shipping_amount"
class="move-up"
prefix="$"
#change="removeFreeAmount"
></v-text-field>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs11>
<v-btn
medium
dark
:disabled="!speed_enabled"
color="green lighten-1"
#click="sendShipping()"
>Save {{ speed }} Settings</v-btn>
<v-btn
medium
outline
:disabled="!speed_enabled"
color="purple darken-3"
#click="deleteShipping"
>Delete {{ speed }} Settings</v-btn>
</v-flex>
</v-layout>
</v-form>
</v-card>
</template>
<script>
import {dataShare} from '../packs/application.js';
import axios from 'axios';
export default {
data: function() {
return {
speed: "Standard",
icon_adjust: {'offset-xs1': false},
icon_push_right: {'push-right': false},
// [`${this.speed}_speed_enabled`]: false,
speed_enabled: false,
flex_enabled: false,
fixed_enabled: false,
free_enabled: false,
flex_switch_disabled: true,
fixed_switch_disabled: true,
fixed_rate_amount: "",
free_shipping_amount: "",
flex_above_or_below_amount: "",
valid: true,
percent_or_dollar: "",
fee_type: ["$", "%"]
};
},
created() {
dataShare.$on('speed', (speed) => {
this.speed = speed;
});
axios.get('https://bc-ship-trimakas.c9users.io/return_speed_info', {params: {speed: this.speed}}).then(response => {
// this.speed = response.data.shipping_speed;
this.speed_enabled = true;
// this.[`${this.speed}_speed_enabled`] = true;
this.fixed_rate_amount = response.data.fixed_amount;
this.fixed_enabled = response.data.fixed;
this.flex_enabled = response.data.flex;
this.flex_above_or_below_amount = response.data.flex_amount;
this.percent_or_dollar = response.data.flex_dollar_or_percent;
this.free_enabled = response.data.free;
this.free_shipping_amount = response.data.free_shipping_amount;
});
},
computed: {
removeFlexAmount(){
if(this.flex_enabled == false){
this.flex_above_or_below_amount = "";
this.percent_or_dollar = "";
}
},
removeFixedAmount(){
if(this.fixed_enabled == false){
this.fixed_rate_amount = "";
}
},
removeFreeAmount(){
if(this.free_enabled == false){
this.free_shipping_amount = "";
}
},
pushIconRight() {
if(this.$vuetify.breakpoint.mdAndDown){
this.icon_push_right['push-right'] = true;
}
else{
this.icon_push_right['push-right'] = false;
}
},
moveIcon() {
console.log(this.$vuetify.breakpoint.name);
if(this.$vuetify.breakpoint.mdAndDown){
this.icon_adjust['offset-xs1'] = true;
}
else{
this.icon_adjust['offset-xs1'] = false;
}
},
enableAndDisable(){
if(this.speed_enabled == true){
this.flex_switch_disabled = false;
this.fixed_switch_disabled = false;
}
else{
this.flex_switch_disabled = true;
this.fixed_switch_disabled = true;
}
},
killFixed(){
if(this.flex_enabled == true){
this.fixed_switch_disabled = true;
this.fixed_rate_amount = "";
}
if(this.speed_enabled == true && this.flex_enabled == true){
this.fixed_switch_disabled = true;
this.fixed_enabled = false;
this.fixed_rate_amount = "";
}
if(this.speed_enabled == true && this.flex_enabled == false){
this.fixed_switch_disabled = false;
}
},
killFlex(){
if(this.fixed_enabled == true){
this.flex_switch_disabled = true;
this.above_or_below_amount = "";
}
if(this.speed_enabled == true && this.fixed_enabled == true){
this.flex_switch_disabled = true;
this.flex_enabled = false;
this.above_or_below_amount = "";
this.percent_or_dollar = "";
}
if(this.speed_enabled == true && this.fixed_enabled == false){
this.flex_switch_disabled = false;
}
}
},
methods: {
sendShipping() {
const shipping = {
speed_type: this.speed,
speed_enabled: this.speed_enabled,
flex_enabled: this.flex_enabled,
fixed_enabled: this.fixed_enabled,
free_enabled: this.free_enabled,
fixed_rate_amount: this.fixed_rate_amount,
free_shipping_amount: this.free_shipping_amount,
standard_flex_above_or_below_amount: this.above_or_below_amount,
percent_or_dollar: this.percent_or_dollar
};
let self = this;
let speed_info = {bytestand_rate_info: shipping};
axios.post('https://bc-ship-trimakas.c9users.io/save_shipping_info', speed_info).then(response => {
console.log(this.response);
});
},
deleteShipping() {
const deleteSpeed = {
speed_type: this.speed
};
let self = this;
let delete_speed = {bytestand_rate_info: deleteSpeed};
axios.post('https://bc-ship-trimakas.c9users.io/delete_speed', delete_speed).then(response => {
console.log(this.response);
});
}
}
};
</script>
<style>
.push_down {
margin-top: 18px;
}
.fix_icon_less {
margin-top: 5px;
margin-left: -70px;
}
.fix_icon {
margin-top: 5px;
margin-left: -80px;
}
.move-up {
margin-top: -18px;
}
.move-left {
margin-left: -20px;
}
.push-right {
margin-left: 10px;
}
</style>
Related
I have a prop and currently am able to get the data of the prop, Am trying a way to capture the item of the prop when saving the form.
Is there a way where i can take the value and pass if in a hidden text-area and bind the data to the vmodel?
Any help I appreciate.
<v-dialog v-model="dialog" persistent max-width="800">
<template v-slot:activator="{ on }">
<v-btn dark v-on="on" color="primary" round> Make payment </v-btn>
</template>
<v-card>
<v-card-title class="headline primary">
<span class="white--text">Add a new Doctor Payment Record {{ queueId }}</span>
<v-btn icon dark #click.native="dialog = false" absolute right>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<users-search
:leave-selected="true"
idOnly
label="Select Doctor"
#results="setDoctor"
>
</users-search>
<div class="row px-3">
<v-autocomplete
class="px-3 col-sm-8"
v-model="expense.bank"
v-if="banks.data"
:items="banks.data"
outline
chips
label="Select bank"
item-text="name"
item-value="id"
>
</v-autocomplete>
<v-text-field
class="px-3 col-sm-8"
outline
flat
v-model="expense.amount"
type="number"
#input="expense.percentage()"
required
label="Amount *"
persistent-hint
/>
</div>
<v-text-field
class="px-3"
outline
flat
v-model="expense.total_paid"
required
label="amount paid"
persistent-hint
/>
<v-text-field
class="px-3"
outline
flat
:value="setQueue"
v-model="expense.queueId"
required
:label=queueId
persistent-hint
/>
<v-alert :value="true" type="error" v-if="errors.any()">
<div v-html="errors.display()"></div>
</v-alert>
<v-layout row wrap>
<v-flex xs12>
<v-btn
color="success"
:loading="saveLoader"
#click="recordExpense()"
>save</v-btn
>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import NewUser from "#finance/libs/users/NewUser";
import {mapActions, mapGetters} from "vuex";
export default {
props: [
'queueId'
],
data: () => ({
dialog: false,
expense: new NewUser(),
saveLoader: false,
}),
computed: {
...mapGetters({
banks: "getBanks",
}),
balance: function () {
return parseFloat(10);
},
submitted() {
return this.expense.form.submitted;
},
contaminated() {
return this.expense.form.errorDetected;
},
errors() {
return this.expense.form.errors;
},
},
watch: {
submitted(v) {
if (v) {
this.saveLoader = false;
}
},
contaminated() {
this.saveLoader = false;
},
},
methods: {
...mapActions({
fetchBanks: "setBanks",
}),
setDoctor(user) {
this.expense.doctor_id = user.id;
},
setQueue(){
console.log(this.queueId);
this.expense.queueId = this.queueId;
},
async recordExpense() {
this.saveLoader = true;
let response = await this.expense.saveExpense();
this.saveLoader = false;
if (response) {
this.dialog = false;
this.$emit("expenseCreated");
}
},
},
mounted() {
this.fetchBanks();
}
};
</script>
The prop queueId i also want to store it along with the user information from the form.
Try this one, it should work:
<template>
<textarea v-model="hiddenValue" :style="{ display: 'none' }"></textarea>
</template>
<script>
export default {
props: [ 'queueId' ],
data() {
return {
hiddenValue: this.queueId
}
}
}
</script>
In case you will no need the prop to be modified, please bind the texarea value to the prop directly:
<textarea hidden v-model="queueId" :style="{ display: 'none' }></textarea>
<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);
},
}
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?
I'm using vue-cropper 4.1 from cropperjs with Nuxt 2.13. when my cropper load it shows a placeholder, after choosing image and uploading it, the cropper still shows the chosen image, but i wanna load the placeholder again. what should i do? i tried replace() but didn't work. here is my code:
note: there are some this in my code that u may not find the reference. they are globally added to my project via mixins. and editProduct is a state in my vuex that is added globally same as it's action.
<template>
<div class="pt-6">
<v-row class="ma-0">
<v-col cols="12" sm="12" md="5" class="pa-0" >
<v-card flat class="pb-4" max-width="450px">
<client-only>
<vue-cropper
ref="cropper"
:aspect-ratio="1/1"
:src="url"
dragMode="move"
/>
</client-only>
<v-card flat class="d-flex justify-center theme__main__color my-2">
<v-btn text icon class="mx-1 text_main_color" #click.prevent="rotate(-45)">
<v-icon>mdi-rotate-left</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" ref="flipX" #click.prevent="flipX">
<v-icon>mdi-flip-horizontal</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" #click.prevent="reset">
<v-icon>mdi-border-all-variant</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" ref="flipY" #click.prevent="flipY">
<v-icon>mdi-flip-vertical</v-icon>
</v-btn>
<v-btn text icon class="mx-1 text_main_color" #click.prevent="rotate(45)">
<v-icon>mdi-rotate-right</v-icon>
</v-btn>
<a style="display:none;" ref="flipX" href="#" #click.prevent="flipX"></a>
<a style="display:none;" ref="flipY" href="#" #click.prevent="flipY"></a>
</v-card>
<v-list container inline class="transparent text-center pa-0">
<v-list-item class="px-0">
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__w text_main_color"
:loading="isSelecting"
#click="onChooseClick"
>{{lang.chooseimage}}</v-btn>
</v-list-item>
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__s text_main_color"
#click.prevent="uploadImage"
:disabled="isUploading || !selectedFile"
>{{lang.upload}}</v-btn>
<input
ref="uploader"
class="d-none"
type="file"
accept="image/*"
#change="onFileChanged"
>
</v-list-item>
</v-list-item>
</v-list>
</v-card>
</v-col>
<v-col cols="12" sm="12" md="7" class="pa-0" v-if="storedImg">
<v-row class="ma-0">
<!-- img 1 -->
<v-col cols="12" sm="6" md="4" class="pa-1 mb-4" v-for="(image, index) in storedImg" :key="index">
<v-card flat>
<div tile class="rounded-r felx float-right text_main_color theme__btn__p pa-1" height="100%">
<div><v-icon class="text_main_color" medium>mdi-sort-variant</v-icon></div>
<div><span class="my-1 productlist__btn__sort">Hold to order</span></div>
</div>
<v-img class="theme__main__color rounded-l" max-width="141" :src="imagePathCreator(image.url)"></v-img>
<v-btn text class="theme__cta__color text_main_color mt-2" min-width="141" #click.prevent="deleteImg(index, image.id)">{{lang.delete}}</v-btn>
</v-card>
</v-col>
</v-row>
</v-col>
<!-- btn -->
<addproductbtn :section="section" />
</v-row>
</div>
</template>
<style>
</style>
<script>
import addproductbtn from '~/components/global/cms/addproductbtn'
import {ALERT_TYPE, ALERT_METHOD} from '~/plugins/constants.js'
import VueCropper from 'vue-cropperjs';
import 'cropperjs/dist/cropper.css';
export default {
components:{
VueCropper,
'addproductbtn': addproductbtn
},
props:['err'],
data(){
return{
isSelecting: false,
selectedFile: null,
url: null,
placeholder: '/images/placeholder/place-800.png',
section: 'img',
pImages: [],
pId: null,
cropData: null,
isUploading: false
}
},
computed:{
storedImg(){
return this.editProduct.images
}
},
methods:{
// ***** Cropper Methods ***** \\
flipX() {
const dom = this.$refs.flipX;
let scale = dom.getAttribute('data-scale');
scale = scale ? -scale : -1;
this.$refs.cropper.scaleX(scale);
dom.setAttribute('data-scale', scale);
},
flipY() {
const dom = this.$refs.flipY;
let scale = dom.getAttribute('data-scale');
scale = scale ? -scale : -1;
this.$refs.cropper.scaleY(scale);
dom.setAttribute('data-scale', scale);
},
reset() {
this.$refs.cropper.reset();
},
rotate(deg) {
this.$refs.cropper.rotate(deg);
},
getData(){
this.cropData = this.$refs.cropper.getData(true);
},
// ***** Component Methods ***** \\
onChooseClick(){
this.isSelecting = true
window.addEventListener('focus', () => {
this.isSelecting = false
}, { once: true })
this.$refs.uploader.click()
},
onFileChanged(e) {
// **** CROPPER CHOOSE FILE **** //
this.selectedFile = e.target.files[0];
if (this.selectedFile.type.indexOf('image/') === -1) {
this.noty(ALERT_TYPE[0],lang.errimagefile);
return;
}
if (typeof FileReader === 'function') {
const reader = new FileReader();
reader.onload = (event) => {
this.url = event.target.result;
// rebuild cropperjs with the updated source
this.$refs.cropper.replace(event.target.result);
};
reader.readAsDataURL(this.selectedFile);
} else {
this.noty(ALERT_TYPE[0],lang.something_wrong);
}
},
async uploadImage(){
this.getData()
if(this.notEmpty(this.selectedFile) && this.notEmpty(this.pId) &&
this.notEmpty(this.cropData)){
this.isUploading = true
const data = new FormData()
data.append('image', this.selectedFile)
data.append('pId', this.pId)
data.append('w', this.cropData.width)
data.append('h', this.cropData.height)
data.append('x', this.cropData.x)
data.append('y', this.cropData.y)
data.append('r', this.cropData.rotate)
data.append('sx', this.cropData.scaleX)
data.append('sy', this.cropData.scaleY)
let response = await this.axiosPost('product/createproimg', data)
if(this.resOk(response.status)){
const newImages = {"id": response.data.id,"url": response.data.url}
this.setEditProductImg(newImages)
this.selectedFile = null
}
}
this.isUploading = false
},
},
created(){
this.url = this.placeholder
}
}
</script>
After searching and reading the document on Cropperjs i finally found the solution.
after image is uploaded i used distroy() and then replace() like this:
async uploadImage(){
this.getData()
if(this.notEmpty(this.selectedFile) && this.notEmpty(this.editProduct.pId) && this.notEmpty(this.cropData)){
// this.isUploading = true
const data = new FormData()
data.append('image', this.selectedFile)
data.append('pId', this.editProduct.pId)
data.append('w', this.cropData.width)
data.append('h', this.cropData.height)
data.append('x', this.cropData.x)
data.append('y', this.cropData.y)
data.append('r', this.cropData.rotate)
data.append('sx', this.cropData.scaleX)
data.append('sy', this.cropData.scaleY)
let response = await this.axiosPost('product/createproimg', data)
if(this.resOk(response.status)){
const newImages = {"id": response.data.id,"url": response.data.url}
this.setEditProductImg(newImages)
this.myArray.unshift(newImages)
this.originalArray.push(newImages)
this.selectedFile = null
//*********** THIS PART REPLACE CROPPER WHITH MY PLACEHOLDER IMAGE **************\\
this.$refs.cropper.destroy()
this.$refs.cropper.replace(this.placeholder)
//**** this.placeholder is the placeholder.jpg file path in my static folder ****\\
this.isUploading = false
}else{
this.isUploading = false
}
}
this.isUploading = false
},
I need help as the array “items1” works when “console.log(this.items1)”
However, it is undefined when “console.log(this.items1[0])”. How can I solve this? I really have no idea what is going. Ultimately, I want to gather item.text whenever user selects the tab from v-tabs. Using item.text, I am able to filter the data in db. Is there a way to gather the user input whenever user selects tab?
<template>
<v-layout>
<v-container flat grid-list-lg>
<v-layout row wrap class="flex_box feature_products">
<v-flex xs12>
<h2 class="text-xs-center feature_products_title">Check Our <span>Delicious Menu</span></h2>
</v-flex>
<v-flex xs12>
<v-card>
<v-toolbar flat>
<template v-slot:extension>
<v-tabs v-model="model" centered slider-color="yellow">
<v-tab v-for="(item, index) in items1" :key="index" :href="`#tab-${index}`">
{{ item.text }}
</v-tab>
</v-tabs>
</template>
</v-toolbar>
<v-tabs-items v-model="model">
<v-tab-item v-for="(item, index) in items1" :key="index" :value="`tab-${index}`">
<!-- your this code displays the product information, but there is no way to filter the product by category -->
<v-layout row wrap class="flex_box feature_products">
<v-flex xs12 sm3 md3 lg3 xl2 class="flex_item" v-for="(product,index) in products" :key="index">
<v-card flat v-if="product.category_name == item.text">
<v-card class="overlay_container flex_wrap pa-2">
<v-img :src="product.image" contain></v-img>
<div style="width:100%;" class="flex_bottom text-xs-center pb-2">
<h3 class="headline text-xs-center grey--text text--darken-3">{{product.item_name}}</h3>
<h4 class="grey--text text--darken-3">{{currency}}{{product.price}}</h4>
</div>
<v-card class="overlay">
<h2 style="vertical-align:middle;">{{product.item_name}}</h2>
<v-list class="details">
<v-list-tile-action>
<v-btn style="width:100%" :to="'/product/' + product.id">Details</v-btn>
</v-list-tile-action>
<v-list-tile-action>
<v-btn style="width:100%" class="main_color white--text" #click="addToCart(product)">Add To Cart</v-btn>
</v-list-tile-action>
</v-list>
</v-card>
</v-card>
</v-card>
<v-card v-else>
</v-card>
</v-flex>
</v-layout>
</v-tab-item>
</v-tabs-items>
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-layout>
</template>
<script>
import Vue from 'vue'
import firebase from "firebase";
import moment from 'moment'
import db, {functions} from '#/firebase/init'
import MenuNavbar from '#/components/shop/navbar/MenuNavbar'
export default {
data(){
// Show All Categories
let ref = db.collection("item_categories");
ref.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type == "added") {
let doc = change.doc;
this.items1.push({
icon: doc.data().category_icon,
text: doc.data().category_name,
link:'CatProduct',
category:doc.data().category_name,
text1: 'Category Name: ' + doc.data().category_name
});
}
});
});
return{
model:'tab-2', // select your default tab here
currency:null,
products:[],
cart:this.$store.getters.cart,
items1:[],
}
},
components: {
MenuNavbar
},
methods: {
productInCart(product) {
const cartItems = this.cart
for (let i = 0; i < cartItems.length; i++) {
if (cartItems[i].product.id === product.id) {
return i
}
}
return null
},
addToCart(product, quantity){
const index = this.productInCart(product)
const productQuantity = (!quantity || quantity < 1) ? 1 : parseInt(quantity)
if (index === null) {
var items = {
product: product,
quantity: productQuantity
}
//this.$store.commit('catalog/updateCart', items)
this.$store.commit('updateCart', items)
}else {
if(!quantity){
// If item is already into the cart then add++ quantity
this.$store.commit('increaseQuantity', index)
}else{
// When quantity updated manually
}
}
},
removeCart(index){
this.$store.commit('removeCart', index)
},
},
computed:{
counter(){
return this.$store.getters.counter
},
},
getTabText(text){
return text
},
created(){
var db = firebase.firestore();
// Current Currency
db.collection("settings").doc('config').onSnapshot(doc =>{
this.currency = doc.data().currency
})
console.log(this.items1[0])
// Show All Items
let cref = db.collection('items').orderBy('timestamp', 'desc').where("featured", "==", true)
cref.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type == 'added'){
let doc = change.doc
this.products.push({
id:doc.id,
item_name:doc.data().item_name,
image:doc.data().image,
category_name:doc.data().item_category,
price:doc.data().price,
quantity:doc.data().quantity,
timestamp:moment(doc.data().timestamp).fromNow('lll')
})
}
})
})
}
}
</script>