Vue-Router: page with params is blank after refreshing - javascript

On initial page, I get 2 ID variables: organizationId and brandId, that I want to save in my URL as params, while routing to Organization Overview Page.
export const router = new Router({
mode: 'history',
routes: [
...
{
path: 'organization/:organizationId/brand/:brandId/overview',
name: 'OrganizationOverviewPage',
component: OrganizationOverviewPage,
},
...
]
});
<router-link :to="{ name: 'OrganizationOverviewPage',
params: { organizationId: owner.id, brandId: brand.id }}">
BUTTON
</router-link>
It works properly, on the new page I can see both variables in URL and in Vue DevTools:
The problem is that when I refresh the page, I got a blank screen.
The interesting thing is that when I include only 'organizationId' variable in the router path, it works properly.
{
path: 'organization/:organizationId/overview',
name: 'OrganizationOverviewPage',
component: OrganizationOverviewPage,
},
This is also how it looked like earlier, but due to the needs of the second variable - brandId, I had to add it - now it does not work.
The URL seems to hit the route because only when I break it, I got 404. Could this be related to the generated component?
I have no idea how to find a solution, I did not even get any errors from the terminal or browser console.
Update - OrganizationOverviewPage:
<template>
<div>
<v-row no-gutters class="mb-3">
<v-col cols="12">
<v-card flat>
<v-card-title>
<h5 class="float-left m-0">
<router-link
:to="{ name: 'OrganizationOverviewPage'}"
class="main-options-router-link"
>
<span class="font-weight-bold text-dark">Overview</span>
</router-link>
<span class="px-1">|</span>
<router-link
class="main-options-router-link"
:to="{ name:'OrganizationArtists'}"
>Artists</router-link>
<span class="px-1">|</span>
<router-link class="main-options-router-link" :to="{ name: 'OrganizationBrands'}">Brands</router-link>
<span class="px-1">|</span>
<router-link class="main-options-router-link" :to="{ name: 'OrganizationTeam'}">Team</router-link>
<span class="px-1">|</span>
<router-link
class="main-options-router-link"
:to="{ name: 'OrganizationTemplates'}"
>Templates</router-link>
</h5>
</v-card-title>
</v-card>
</v-col>
</v-row>
<v-row>
<v-col md="6">
<OrganizationProfileWidget></OrganizationProfileWidget>
</v-col>
<v-col md="6">
<OrganizationMetricsWidget v-bind:organizationId="organizationId"></OrganizationMetricsWidget>
</v-col>
</v-row>
<v-row no-gutters class="mt-3">
<v-col cols="12">
<Widget
title="<h4><span class='font-weight-bold'>Active Campaigns</span></h4>"
customHeader
settings
:name="`Organization Overview`"
>
<div slot="body">
<v-row>
<v-col class="mt-4 mb-2">
<v-text-field
outlined
v-model="search"
dense
placeholder="Search user by Name"
hide-details
></v-text-field>
</v-col>
</v-row>
<v-data-table
:headers="headers"
:items="list"
item-key="id"
:search="search"
class="elevation-1 list-data-table"
:loading="inProgress"
hide-default-footer
:page.sync="page"
:items-per-page="itemsPerPage"
#page-count="pageCount = $event"
>
<template v-slot:item.artistName="{ item }">
<router-link
:to="{ name:'ArtistOpsPage',params: { organizationId: organizationId,artistId:item.artistId }}"
>{{item.artistName}}</router-link>
</template>
<template v-slot:item.title="{ item }">
<router-link
:to="{ name:'ArtistOpsPage',params: { organizationId: organizationId,artistId:item.artistId }}"
>{{item.title}}</router-link>
</template>
<template v-slot:item.startDt="{ item }">{{item.startDt | DayMonthYear}}</template>
<template v-slot:item.endDt="{ item }">{{item.endDt | DayMonthYear}}</template>
<template v-slot:footer>
<v-divider></v-divider>
<v-row align="center">
<v-col cols="2" class="px-4">
<v-select
:value="itemsPerPage"
:items="perPageRecordsOptions"
label="Items per page"
#change="itemsPerPage = parseInt($event, 10)"
></v-select>
</v-col>
<v-spacer></v-spacer>
<v-col cols="8" class="px-4">
<v-pagination v-model="page" :length="pageCount"></v-pagination>
</v-col>
<v-spacer></v-spacer>
</v-row>
</template>
</v-data-table>
</div>
</Widget>
</v-col>
</v-row>
</div>
</template>
<script>
import Widget from "#/components/venus/Widget";
import { createHelpers } from "vuex-map-fields";
import OrganizationProfileWidget from "#/components/venuspro/agent/suitcase/OrganizationProfileWidget";
import OrganizationMetricsWidget from "#/components/venuspro/agent/suitcase/OrganizationMetricsWidget";
const { mapFields } = createHelpers({
getterType: "organizationOne/getField",
mutationType: "organizationOne/updateField"
});
export default {
name: "OrganizationOverviewPage",
components: {
OrganizationProfileWidget,
OrganizationMetricsWidget,
Widget
},
computed: {
organizationId: function() {
return this.$route.params.organizationId;
},
brandId: function() {
return this.$route.params.brandId;
},
list: function() {
return this.$store.state.campaignList.allList;
},
organizationOne: function() {
return this.$store.state.organizationOne.one;
},
...mapFields(["one", "inProgress"])
},
data() {
return {
search:"",
page: 1,
itemsPerPage: 10,
perPageRecordsOptions: [5, 10, 50, 100, 500],
headers: [
{
text: "Artist",
align: "center",
sortable: false,
value: "artistName"
},
{
text: "Title",
align: "center",
sortable: true,
filterable: true,
value: "title"
},
{
text: "Start Date",
align: "center",
sortable: true,
filterable: true,
value: "startDt"
},
{
text: "End Date",
align: "center",
sortable: true,
filterable: true,
value: "endDt"
}
]
};
},
created() {
if (this.organizationOne.id !== this.organizationId)
this.$store.dispatch("organizationOne/fetchOne", this.organizationId);
this.$store.dispatch(
"campaignList/fetchAllCampaignList",
this.organizationId
);
},
methods: {}
};
</script>

Someone answer under this question with a hint, but then removed his answer. Thank You anyway, that was a solution!
In my vue.config.js, it was the configuration in my devServer object that I needed to fix.
module.exports = {
...
devServer: {
...
historyApiFallback: {
rewrites: [
...
// { from: /\/brand/, to: '/brand.html' },
]
}
},
}
I have commented out the line with brand rewriting, and now it's working!

Related

How can I pre-populate my input with data from API?

I have an edit form, and I'm trying to populate my input base on the response from API. My campaign data look like this.
{
"id": 219,
"name": "finishedddd-1642606412049"
}
Testing
You see that I can access campaign.name like this
<p>Campaign Name : {{ campaign.name }}</p>
Trying
I want to pre-populated my name input, so I did this
data() {
return {
campaign: {},
form: {
errors: {},
values: {
name: this.campaign.name,
...
Result
Somehow that kept getting me :
"TypeError: Cannot read properties of undefined (reading 'name')"
Code
<template>
<v-container fluid class="my-1">
<Navbar />
<Breadcrumbs />
<v-row>
<v-col cols="12">
<v-card elevation="2">
<PanelHeader type="create" icon="campaign" title="Campaigns" />
<v-stepper v-model="e1" justify="center" elevation="0">
<v-stepper-header>
<v-stepper-step :complete="e1 > 1" step="1"> Campaign </v-stepper-step>
<v-divider></v-divider>
<v-stepper-step :complete="e1 > 2" step="2"> Setup </v-stepper-step>
<v-divider></v-divider>
<v-stepper-step step="3"> Finish </v-stepper-step>
</v-stepper-header>
<v-stepper-items>
<v-stepper-content step="1">
<v-card class="mb-12" elevation="0">
<v-form ref="form" lazy-validation v-model="valid" id="form">
<v-row>
<v-card-text class="font-weight-bold">
Campaigns
<p>Campaign Name : {{ campaign.name }}</p>
<v-tooltip right>
<template v-slot:activator="{ on, attrs }">
<v-btn icon v-bind="attrs" v-on="on">
<v-icon color="grey lighten-1">info</v-icon>
</v-btn>
</template>
<span>Select marketing campaign type for Print Materials or Product Tags. Provide a name to identify the marketing campaign.</span>
</v-tooltip>
</v-card-text>
</v-row>
<v-row>
<v-col class="col-sm-2 col-lg-2 col-12">
<v-select disabled dense outlined :items="types" label="Type" v-model="form.values.type" :rules="form.rules.type"></v-select>
</v-col>
<v-col cols="12" sm="6" md="2">
<v-text-field dense outlined v-model="form.values.name" :rules="form.rules.name" label="Name" required></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-textarea dense rows="1" outlined v-model="form.values.description" label="Description" required></v-textarea>
</v-col>
</v-row>
<v-row>
<v-card-text class="font-weight-bold"
>Schedule :
<v-tooltip right>
<template v-slot:activator="{ on, attrs }">
<v-btn icon v-bind="attrs" v-on="on">
<v-icon color="grey lighten-1">info</v-icon>
</v-btn>
</template>
<span>Set the time zone, start and end date for this campaign to be active.</span>
</v-tooltip>
</v-card-text>
<v-col cols="12" sm="6" md="4">
<v-select dense outlined :items="timezones" v-model="form.values.timezone" :rules="form.rules.timezone" label="Timezone" append-icon="lock_clock"></v-select>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6" md="2">
<v-menu v-model="form.values.startDateMenu" :close-on-content-click="false" :nudge-right="40" transition="scale-transition" offset-y min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field dense outlined v-model="form.values.startDate" :rules="form.rules.startDate" label="Start Date" append-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="form.values.startDate"></v-date-picker>
</v-menu>
</v-col>
<v-col cols="12" sm="6" md="2">
<v-menu ref="menu" v-model="startTimeMenu" :close-on-content-click="false" :nudge-right="40" :return-value.sync="form.values.startTime" transition="scale-transition" offset-y max-width="290px" min-width="290px">
<template v-slot:activator="{ on, attrs }">
<v-text-field dense v-model="form.values.startTime" label="Start Time" append-icon="mdi-clock-time-four-outline" readonly v-bind="attrs" v-on="on" outlined></v-text-field>
</template>
<v-time-picker v-if="startTimeMenu" v-model="form.values.startTime" full-width #click:minute="$refs.menu.save(form.values.startTime)"></v-time-picker>
</v-menu>
</v-col>
</v-row>
<v-row>
<v-col cols="12" sm="6" md="2">
<v-menu v-model="endDateMenu" :close-on-content-click="false" :nudge-right="40" transition="scale-transition" offset-y min-width="auto">
<template v-slot:activator="{ on, attrs }">
<v-text-field dense outlined v-model="form.values.endDate" :rules="form.rules.endDate" :min="form.values.startDate" label="End Date" append-icon="mdi-calendar" readonly v-bind="attrs" v-on="on"></v-text-field>
</template>
<v-date-picker v-model="form.values.endDate"></v-date-picker>
</v-menu>
</v-col>
<v-col cols="12" sm="6" md="2">
<v-menu ref="menu" v-model="endTimeMenu" :close-on-content-click="false" :nudge-right="40" :return-value.sync="form.values.endTime" transition="scale-transition" offset-y max-width="290px" min-width="290px">
<template v-slot:activator="{ on, attrs }">
<v-text-field dense v-model="form.values.endTime" label="End Time" append-icon="mdi-clock-time-four-outline" readonly v-bind="attrs" v-on="on" outlined></v-text-field>
</template>
<v-time-picker v-if="endTimeMenu" v-model="form.values.endTime" :min="form.values.startTime" full-width #click:minute="$refs.menu.save(form.values.endTime)"></v-time-picker>
</v-menu>
</v-col>
</v-row>
</v-form>
</v-card>
<v-btn color="primary" #click="validate()" :disabled="!valid"> Continue </v-btn>
<router-link :to="`/${segment1}`">
<v-btn text> Cancel </v-btn>
</router-link>
</v-stepper-content>
<v-stepper-content step="2">
<v-card class="mb-12" elevation="0">
<v-form ref="form2" lazy-validation v-model="valid2" id="form2">
<v-row>
<v-card-text class="font-weight-bold">
Destination
<v-tooltip right>
<template v-slot:activator="{ on, attrs }">
<v-btn icon v-bind="attrs" v-on="on">
<v-icon color="grey lighten-1">info</v-icon>
</v-btn>
</template>
<span>The scan destination is the end point for a consumer experience. Can be single URL or use URL Groups. </span>
</v-tooltip>
</v-card-text>
</v-row>
<v-row>
<v-col class="col-sm-2 col-lg-2 col-12">
<v-select dense outlined :items="urlTypes" label="Single or Multi URL" v-model="form.values.urlType" :rules="form.rules.urlType"></v-select>
</v-col>
<v-col cols="12" sm="6" md="2">
<v-text-field dense outlined v-model="form.values.url" :rules="form.rules.url" label="URL" required></v-text-field>
</v-col>
</v-row>
<v-row>
<v-card-text class="font-weight-bold"
>Conditions :
<v-tooltip right>
<template v-slot:activator="{ on, attrs }">
<v-btn icon v-bind="attrs" v-on="on">
<v-icon color="grey lighten-1">info</v-icon>
</v-btn>
</template>
<span>Set the conditions for a campaign. If all conditions are true, this campaign will trigger for consumer experience.</span>
</v-tooltip>
</v-card-text>
<v-col cols="12" sm="6" md="4">
<v-select dense outlined :items="attributes" item-text="name" item-value="id" v-model="form.values.attribute" :rules="form.rules.attribute" label="Attribute"></v-select>
</v-col>
<v-col cols="12" sm="6" md="1">
<v-combobox v-model="operator" :items="operators" item-text="operator" item-value="id" label="Operator" outlined dense></v-combobox>
</v-col>
<v-col cols="12" sm="6" md="4">
<!-- <v-text-field dense outlined v-model="form.values.value" :rules="form.rules.value" label="Values" required></v-text-field> -->
<v-text-field v-model="value" :items="values" label="Value" multiple outlined dense></v-text-field>
</v-col>
</v-row>
</v-form>
</v-card>
<v-btn color="primary" #click="validate2()" :disabled="!valid2"> Update </v-btn>
<v-btn text #click="e1 = 1"> Back </v-btn>
</v-stepper-content>
<v-stepper-content step="3"> </v-stepper-content>
</v-stepper-items>
</v-stepper>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
import Navbar from '../../../components/Navbar'
import Breadcrumbs from '../../../components/Breadcrumbs'
import PanelHeader from '../../../components/PanelHeader'
import axios from 'axios'
import moment from 'moment-timezone'
export default {
components: {
Navbar,
Breadcrumbs,
PanelHeader
},
beforeMount() {},
computed: {
segment1: function () {
const firstSegment = new URL(window.location.href).pathname.split('/')[1]
return `${firstSegment}`
},
timeZone: function () {
console.log('timeZone')
}
},
beforeMount() {},
mounted() {
this.getCampaign()
},
data() {
return {
campaign: {},
form: {
errors: {},
values: {
name: this.campaign.name,
type: 'Marketing',
description: null,
timezone: 'America/New_York',
startDate: new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().substr(0, 10),
endDate: new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().substr(0, 10),
startTime: moment().format('HH:mm'),
endTime: '24:00',
urlType: 'Single',
url: 'https://',
attribute: '',
operator: '',
value: ''
},
rules: {
type: [(v) => !!v || 'Type is required'],
name: [(v) => !!v || 'Name is required'],
startDate: [(v) => !!v || 'Start Date is required'],
endDate: [(v) => !!v || 'End Date is required'],
timezone: [(v) => !!v || 'Timezone is required'],
startTime: [(v) => !!v || 'Start Time is required'],
endTime: [(v) => !!v || 'End Time is required'],
urlType: [(v) => !!v || 'URL Type is required'],
url: [(v) => !!v || 'URL is required'],
attribute: [(v) => !!v || 'Attribute is required'],
operator: [(v) => !!v || 'Operator is required'],
value: [(v) => !!v || 'Value is required']
}
},
e1: 1,
valid: false,
valid2: false,
types: ['Product', 'Marketing'],
operator: [],
operators: ['=', '!=', 'in', 'not in'],
value: [],
values: ['Italy', 'Finland', 'Norway'],
timezones: moment.tz.names(),
startDateMenu: false,
endDateMenu: false,
startTimeMenu: false,
endTimeMenu: false,
urlTypes: ['Single', 'Multiple'],
attributes: []
}
},
watch: {
'form.values.attribute'() {
this.operator = null
axios.defaults.headers['Content-Type'] = 'application/json'
let data = {
$root: 'vc_operator',
op: 'read',
brand: 'COLM',
selection: {
filters: [`id:${this.form.values.attribute}`]
},
_SESSION: localStorage.getItem('session')
}
axios.post(`${process.env.VUE_APP_API_ENDPOINT_URL}`, data).then((response) => {
this.operators = response.data.operators
})
}
},
methods: {
getAllData(id) {
let myForm = document.getElementById(id)
console.log(
Array.from(myForm.elements).map((e) => {
return e.value
})
)
},
validate() {
this.$refs.form.validate()
if (this.$refs.form.validate()) {
let data = {
$root: 'vc_rule_attribute',
op: 'read',
brand: 'COLM',
_SESSION: localStorage.getItem('session')
}
axios.defaults.headers['Content-Type'] = 'applcation/json'
axios.post(`${process.env.VUE_APP_API_ENDPOINT_URL}`, data).then((response) => {
this.attributes = response.data.rule_attributes
})
this.e1 = 2
console.info(this.form.values)
} else {
console.info(this.form.values)
}
},
validate2() {
this.$refs.form2.validate()
if (this.$refs.form2.validate()) {
let data = {
id: this.form.values.id,
name: this.form.values.name,
description: this.form.values.description,
start_date: this.form.values.startDate,
end_date: this.form.values.endDate,
priority: '100',
status_id: 1,
type_id: 1
}
let body = {
$root: 'vc_campaign',
op: 'update',
brand: 'COLM',
campaigns: [data],
_SESSION: localStorage.getItem('session')
}
// this.$store
// .dispatch('editCampaign', body)
// .then(() => {
// this.$router.push({
// path: `/campaigns`
// })
// })
// .catch((err) => {
// console.log('Something went wrong: ', err)
// })
} else {
console.info(this.form.values)
}
},
displayTime(time) {
time = time.split(':')[0]
if (time > 12) {
return 'PM'
} else {
return 'AM'
}
},
getCampaign() {
let filters = 'id:' + this.$route.params.id
let body = {
$root: 'vc_campaign',
op: 'read',
brand: 'COLM',
selection: {
filters: [filters]
},
_SESSION: localStorage.getItem('session')
}
axios.defaults.headers['Content-Type'] = 'applcation/json'
axios
.post(`${process.env.VUE_APP_API_ENDPOINT_URL}`, body)
.then((response) => {
if (response.data.status == 0) {
this.campaign = response.data.campaigns[0]
} else {
alert(response.data.statustext)
reject(response.data.statustext)
}
})
.catch((err) => {
console.log('Something went wrong: ', err)
})
}
}
}
</script>
<style></style>
You are basically trying to access a variable which is not defined yet. data() is called only once when you initialize the component. This happens way before you get the response from api and assign a value to this.campaign.
That's why this line doesn't work:
name: this.campaign.name
What you can do instead is something like this:
data() {
return {
campaign: {},
form: {
errors: {},
values: {
name: "",
...
getCampaign() {
...
axios.post(`${process.env.VUE_APP_API_ENDPOINT_URL}`, body)
.then((response) => {
if (response.data.status == 0) {
this.campaign = response.data.campaigns[0]
this.form.values.name = this.campaign.name
...
Keeping your requirements in mind, you can use watch property to keep both the values in sync
Set the initial form.values.name to ''
data() {
return {
campaign: {},
form: {
errors: {},
values: {
name: '',
...
and to keep the values in sync, you can do something like this:
watch: {
campaign: {
handler(newVal) {
this.form.values.name = newVal.name
},
immediate: true
}
}
Try
<p>Campaign Name : {{ campaign?.name }}</p>

vue.runtime.esm.js?TypeError: Cannot read properties of undefined

I create this component from the vuetify documentation.
https://github.com/vuetifyjs/vuetify/blob/master/packages/docs/src/examples/v-card/prop-outlined.vue
<template>
<v-card class="mx-auto" max-width="344" outlined>
<v-list-item three-line>
<v-list-item-content>
<div class="text-overline mb-4">OVERLINE</div>
<v-list-item-title class="text-h5 mb-1"> {{ person.name }} </v-list-item-title>
<v-list-item-subtitle> {{ person.role }} </v-list-item-subtitle>
</v-list-item-content>
<v-list-item-avatar tile size="80" color="grey"></v-list-item-avatar>
</v-list-item>
<v-card-actions>
<v-btn outlined rounded text> Message </v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: 'Person',
props: {
person: Object
}
}
</script>
I import them like so... was intended to use it in a loop 5 times.
<template>
<div class="teams">
<h1 class="subtitle-1 grey--text">Teams</h1>
<v-container class="my-5">
<v-card class="mx-12 my-12">
<v-row>
<v-flex xs12 sm6 md4 lg3 v-for="person in team" :key="person.name">
<Person :name="person" :role="person" />
</v-flex>
</v-row>
<v-divider></v-divider>
</v-card>
</v-container>
</div>
</template>
<script>
import Person from '#/components/Person.vue'
export default {
name: 'Team',
components: {
Person
},
data() {
return {
team: [
{ name: 'The Net Ninja', role: 'Web developer' },
{ name: 'Ryu', role: 'Graphic designer' },
{ name: 'Chun Li', role: 'Web developer' },
{ name: 'Gouken', role: 'Social media maverick' },
{ name: 'Yoshi', role: 'Sales guru' }
]
}
}
}
</script>
However, it is not compiling... I kept getting
vue.runtime.esm.js?2b0e:1897 TypeError: Cannot read properties of undefined (reading 'name')
What did I forget to do ??
If I comment out the
<Person :name="person" :role="person" />
Result
{{ person.name }} seems accessible...
I think it has something to do with the data being rendered after the html. You can probably solve this by:
Adding v-if on the component; only render the component if data exists. You can also add it on v-flex component but as far as I know it's a bad practice because it may disturb the flow.
<v-flex xs12 sm6 md4 lg3 v-if="person" v-for="person in team" :key="person.name">
<Person />
</v-flex>
or alternatively:
<Person v-if="person" />
Add a default value on Person component props
// Object with a default value
person: {
type: Object,
default: function () {
return { name: '' }
}
}
More about props: https://v2.vuejs.org/v2/guide/components-props.html

Handle limit data with 2 buttons (Next and previous button ) in vuejs

In my work, the concept look like have lot of data in array , but the first one i want to show only 4 object (id 1 to id 4) in array. So , next i press next button that i want to loop the next object in array (id 5 to id 8) , then i press the back button they return to previous object that i pass them . i don't know the logic for this.
<template>
<v-container fluid>
<v-row no-gutters>
<v-col cols="12" md="12" sm="12">
<v-card>
<v-row no-gutters>
<v-col cols="12" md="12" sm="12" class="">
<v-row no-gutters class="d-flex">
<v-col
cols="6"
md="6"
v-for="(item, index) in content"
:key="`item-${index}`"
>
<v-col cols="6">
<v-card-text>
<p>{{ item.id }}</p>
<p>my name is : {{ item.title }}</p>
</v-card-text></v-col
>
</v-col>
</v-row>
</v-col>
</v-row>
<v-col cols="12" md="12" sm="12" class="d-flex">
<v-col cols="6" md="6" sm="6">
<v-btn medium elevation="" color="primary" #click="backpage()"
>Back</v-btn
>
</v-col>
<v-col cols="6" md="6" sm="6">
<v-btn medium elevation="" color="primary" #click="nextPage()"
>Next</v-btn
>
</v-col>
</v-col>
</v-card>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
data: () => ({
showCounted: 4,
content: '',
nextValue: Number,
user: [
{
id: 1,
title: 'Saysana',
},
{
id: 2,
title: 'Binh',
},
{
id: 3,
title: 'Say',
},
{
id: 4,
title: 'Q',
},
{
id: 5,
title: 'a',
},
{
id: 6,
title: 'b',
},
{
id: 7,
title: 'c',
},
{
id: 8,
title: 'e',
},
{
id: 9,
title: 'e',
},
{
id: 10,
title: 'e',
},
{
id: 11,
title: 'e',
},
{
id: 12,
title: 'e',
},
],
}),
computed: {
defaultPage() {
return (this.content = this.user.slice(0, this.showCounted))
},
},
methods: {
async nextPage() {
this.content = this.user.slice(this.showCounted, this.showCounted + 4)
console.log(this.content)
this.showCounted = this.showCounted + 4
},
defaultData() {
this.content = this.user.slice(0, this.showCounted)
},
async backpage() {
const previous = -this.showCounted - 4
let to = 4
this.content = this.user.slice(previous, to)
},
},
mounted() {
this.defaultData()
},
}
</script>
Try like following snippet:
Add data property currentPage: 1, then in next/back page increment/decrement current page and slice(this.showCounted * this.currentPage, this.showCounted * this.currentPage + this.showCounted) / slice(this.showCounted * this.currentPage - this.showCounted, this.showCounted * this.currentPage). Also you can disable/enable buttons if you are on last/first page. This way you can chose whatever number of items you want to show just set showCounted to desired value.
Vue.config.productionTip = false
Vue.config.devtools = false
new Vue({
el: '#demo',
vuetify: new Vuetify(),
data(){
return {
showCounted: 4,
currentPage: 1,
content: '',
nextValue: Number,
user: [
{id: 1, title: 'Saysana'}, {id: 2,title: 'Binh'}, {id: 3, title: 'Say'},
{id: 4, title: 'Q'}, {id: 5, title: 'a'}, {id: 6, title: 'b'},
{id: 7, title: 'c'}, {id: 8, title: 'e'}, {id: 9, title: 'e'},
{id: 10, title: 'e'}, {id: 11, title: 'e'},{id: 12, title: 'e'},
],
isDisabledN: false,
isDisabledB: false
}
},
methods: {
defaultData() {
this.content = this.user.slice(0, this.showCounted)
},
nextPage() {
this.content = this.user.slice(this.showCounted * this.currentPage, this.showCounted * this.currentPage + this.showCounted)
this.currentPage++
this.checkPage()
},
backpage() {
this.currentPage--
this.content = this.user.slice(this.showCounted * this.currentPage - this.showCounted, this.showCounted * this.currentPage)
this.checkPage()
},
checkPage() {
if (this.showCounted * this.currentPage >= this.user.length) {
this.isDisabledN = true
this.isDisabledB = false
} else if(this.currentPage <= 1) {
this.isDisabledN = false
this.isDisabledB = true
} else {
this.isDisabledN = false
this.isDisabledB = false
}
}
},
mounted() {
this.defaultData()
this.checkPage()
},
})
<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">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<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>
<div id="demo">
<v-app>
<v-main>
<v-container fluid>
<v-row no-gutters>
<v-col cols="12" md="12" sm="12">
<v-card>
<v-row no-gutters>
<v-col cols="12" md="12" sm="12" class="">
<v-row no-gutters class="d-flex">
<v-col
cols="6"
md="6"
v-for="(item, index) in content"
:key="`item-${index}`"
>
<v-col cols="6">
<v-card-text>
<p>{{ item.id }}</p>
<p>my name is : {{ item.title }}</p>
</v-card-text></v-col
>
</v-col>
</v-row>
</v-col>
</v-row>
<v-col cols="12" md="12" sm="12" class="d-flex">
<v-col cols="6" md="6" sm="6">
<v-btn :disabled="isDisabledB" ref="back" medium elevation="" color="primary" #click="backpage()"
>Back</v-btn
>
</v-col>
<v-col cols="6" md="6" sm="6">
<v-btn :disabled="isDisabledN" ref="next" medium elevation="" color="secondary" #click="nextPage()"
>Next</v-btn
>
</v-col>
</v-col>
</v-card>
</v-col>
</v-row>
</v-container>
</v-main>
</v-app>
</div>

VueJs - I don't understand how I could pass a Boolean value to a chid component

I know that the subject was certainly treated somewhere, but I really don't understand how I could pass a variable to a child component.
What I'm trying to do, is passing 'displayTogglingMenu' in the parent component to 'toggle' in the child.
Parent component
data(){
return {
selectedItem: 1,
displayTogglingMenu: false,
items: [
{ text: 'Home', name: 'home', icon: 'mdi-home' },
{ text: 'Courses', name: 'courses_index', icon: 'mdi-school' },
{ text: 'Enrolments', name: 'enrolments_index', icon: 'mdi-format-list-bulleted' },
{ text: 'Lecturers', name: 'lecturers_index', icon: 'mdi-account-tie' },
],
}
},
Child Component
data(){
return {
toggle: valueOfParentComponent,
alertMessage: '',
loadTable: true,
courses: [],
expendable: [],
...
},
Here is the component I'm trying to hide, as asked by Tim:
<div class="table-container">
<b class="circle"></b>
<v-app>
<v-content>
<v-card>
<v-card-title>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
class="search"
></v-text-field>
<v-spacer></v-spacer>
<router-link :to="{ name: 'new_course'}" class="newItem">New course</router-link>
</v-card-title>
<v-data-table
:headers="courseHeaders"
:items="courses"
:items-per-page="10"
:loading="loadTable"
loading-text="Loading... Please wait"
:search="search"
:single-expand="singleExpand"
:expanded.sync="expanded"
item-key="id"
show-expand
class="elevation-1"
>
<template v-slot:item.title="{ item }">
<transition-group name="list" tag="p">
<span class=" list-item" v-bind:key="item">{{item.title}}</span>
</transition-group>
</template>
<template v-slot:item.actions="{ item }">
<transition-group name="list" tag="p">
<span class=" list-item" v-bind:key="item">
<router-link :to="{ name: 'edit_course', params: { id: item.id } }" class="edit-btn" title="Edit">
<v-icon med>mdi-pencil</v-icon>
</router-link>
<v-btn v-on:click="deleteCourse(item.id)" class="del-btn " title="Delete" >
<v-icon med>mdi-delete</v-icon>
</v-btn>
</span>
</transition-group>
</template>
<template v-slot:expanded-item="{ headers, item }" >
<td :colspan="headers.length" class="item-description" >
<h4 >Course description:</h4>
<p >{{ item.description }}</p>
</td>
</template>
</v-data-table>
</v-card>
</v-content>
</v-app>
<b class="circle2"></b>
</div>
Parent
<template>
<Component :toggle="displayTogglingMenu" />
</template>
<script lang='ts'>
import Component from 'Componenet.vue'
import { defineComponent } from 'vue';
export default defineComponent({
components: {Component},
data() {
return {
displayTogglingMenu: false
}
}
});
</script>
Child
<template>
<div v-show="toggle"></div>
</template>
<script lang='ts'>
import { defineComponent } from 'vue';
export default defineComponent({
props: ['toggle']
});
</script>
The value of displayTogglingMenu in parent will be availible as toggle in the child
You might need to get the value as this.$props.toggle depending on the context
You could pass data from parent to child components using properties. Here are the docs
Eg.
<child-componenet :prop-name="parent value that could also be reactive"></child-component>
Props are reactive so your child-component will react to any change on its properties, you could use the property in a v-if or v-show

How to validate a v-edit-dialog in a v-datatable

I'm new to vue.js and vuetify and currently trying to validate the input in a v-edit-dialog inside a v-datatable. The validation seems to work, however the save button does not get disabled and the user-input gets saved even when it's invalid. Is there a way to let the button be disabled or to prevent the saving of invalid data?
My datatable:
<v-data-table dense :headers="headers" :items="sitesTable">
<template v-slot:top>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<v-card>
<v-toolbar flat>
<v-toolbar-title v-model="title" class="primary--text">{{ title }} Site</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon #click="close">
<v-icon>mdi-close</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-container>
<FormTemplateSites v-bind:title="title" #dialog="!dialog"></FormTemplateSites>
</v-container>
</v-card-text>
</v-card>
</v-dialog>
</template>
<template v-slot:item.name="props">
<v-edit-dialog :return-value.sync="props.item.name" large persistent #save="saveName(props.item.name, props.item)">
<div>{{ props.item.name }}</div>
<template v-slot:input>
<div class="mt-4 title">Update Name</div>
</template>
<template v-slot:input>
<v-text-field v-model="props.item.name" :rules="required" label="Edit" single-line counter autofocus></v-text-field>
</template>
</v-edit-dialog>
</template>
<template v-slot:item.field="props">
<v-edit-dialog :return-value.sync="props.item.field" large persistent #save="saveField(props.item.field, props.item)">
<div>{{ props.item.field }}</div>
<template v-slot:input>
<div class="mt-4 title">Update Field</div>
</template>
<template v-slot:input>
<v-text-field v-model="props.item.field" :rules="rules_vectors" label="Edit" single-line counter autofocus></v-text-field>
</template>
</v-edit-dialog>
</template>
<template v-slot:item.position="props">
<v-edit-dialog :return-value.sync="props.item.position" large persistent #save="savePosition(props.item.position.x, props.item.position.y, props.item)">
<div>{{ props.item.position }}</div>
<template v-slot:input>
<div class="mt-4 title">Update Position</div>
</template>
<template v-slot:input>
<v-text-field v-model="props.item.position" label="Edit" single-line autofocus :rules="rules_vectors"></v-text-field>
</template>
</v-edit-dialog>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" #click="editSite(item)">mdi-pencil</v-icon>
<v-icon small #click="deleteItem(item)">mdi-delete</v-icon>
</template>
</v-data-table>
My component:
data: () => ({
title: '',
dialog: false,
required: [(v) => !!v || 'Required', ],
rules_vectors: [
(v) => !!v || 'Required',
(v) => {
try {
v = JSON.parse(v)
for (let item of v) {
if (!isNaN(item)) {
console.log(item)
} else {
return 'Invalid vector.'
}
}
return v.length === 2 || 'Invalid vector.'
} catch (error) {
console.log(error)
return 'Invalid vector.'
}
},
],
pagination: {},
headers: [{
text: 'Name',
align: 'start',
sortable: false,
value: 'name',
},
{
text: 'ID',
align: 'start',
sortable: false,
value: 'id',
},
{
text: 'Position',
sortable: false,
value: 'position',
},
{
text: 'Field',
sortable: false,
value: 'field',
},
{
text: 'Actions',
value: 'actions',
sortable: false,
},
],
}),
I had the same question
Is there a way to let the button be disabled or to prevent the saving of invalid data?
I found a way to de-couple the item value passed from the editted value and "prevent the saving of invalid data".
By using a seperate data field editName and linking the v-text-field to this, you can set the value on open and check and set it on save, without the item value becoming polluted by invalid values.
So
the v-model for the v-text-field is v-model="editName"
the v-edit-dialog has #open which sets the edit value - #open="editName = props.item.name"
you check the this.editName on save in your saveName(props.item)
as in
<v-data-table dense :headers="headers" :items="sitesTable">
<template v-slot:item.name="props">
<v-edit-dialog
large
persistent
#save="saveName(props.item)"
#open="editName = props.item.name"
>
<div>{{ props.item.name }}</div>
<template v-slot:input>
<div class="mt-4 title">Update Name</div>
</template>
<template v-slot:input>
<v-text-field
v-model="editName"
:rules="required"
label="Edit"
single-line
counter
autofocus
></v-text-field>
</template>
</v-edit-dialog>
</template>
</v-data-table>
on Save
saveName(item) {
if (this.validate(this.editName) {
item.name = this.editName
...
}
}
Edit. I removed the :return-value.sync="props.item.name" attribute on v-data-table as it seemed to be over-riding the setting of item.name in the saveName() function
I ran into the same thing and with some searching it turns out v-edit-dialog is going to be removed in v3 ... which doesn't help us now, but I did come up with a nimble solution that might work for you too.
<v-form v-model="isFormValid">
<v-data-table
:headers="headers"
:items="items"
item-key="ID"
>
<template v-slot:item.column="{ item }">
<v-text-field
v-model="itemData"
:rules="[validationRulesHere]"
required
></v-text-field>
</template>
</v-data-table>
<v-btn :disabled="!isFormValid">Save</v-btn>
</v-form>
I wrapped the whole table in a form and used it to overall tell me if everything in the table was correct.

Categories

Resources