In my Table component, I'm using Vue Bootstrap's b-table component to create a table, which retrieves its' data from an external JSON file through Vuex. Now I also have another component, Actions, which is rendered on each row of the table. This component contains an edit button which is supposed to open a modal when clicked.
The problem is that whenever I click the edit button, 4 modals come up one on top of another. The issue seems to lie in the number of rows rendered, because in the JSON file, there are 4 objects, each of which contains the student's name, date of birth and so on. When I get rid of three of these objects, the modal only renders once. My conclusion is that the modal is rendering 4 times, for each row, but I have no idea how to fix this.
Here's the Table and Actions component:
<script>
import Actions from "./Actions.vue"
export default {
data() {
return {
fields: [
'index',
'full_name',
{ key: "date_of_birth", label: 'Date of Birth' },
'municipality',
{ key: "action", label: 'Action' }
],
// tableItems: this.$store.state.registeredStudents.registeredStudents
}
},
components: {
Actions
},
methods: {
generateIndex() {
return Math.floor(1000000 * Math.random()).toString().slice(0, 6);
}
},
computed: {
rows() {
return this.tableItems.length
},
tableItems() {
const registeredStudents = this.$store.state.registeredStudents.registeredStudents
return registeredStudents.map(student => ({
index: this.generateIndex(), ...student
}))
}
},
}
</script>
<template>
<b-table :fields="fields" :items="tableItems" :per-page="perPage" :current-page="currentPage" responsive="sm" primary-key="index"
striped hover>
<template #cell(action)="data">
<Actions/>
</template>
</b-table>
</template>
<script>
import { BIconPencilFill, BIconTrashFill } from 'bootstrap-vue';
export default {
}
</script>
<template>
<div>
<b-button variant="primary" class="mx-1 p-1" v-b-modal.edit-student>
<b-icon-pencil-fill></b-icon-pencil-fill>
</b-button>
<b-modal id="edit-student" title="Edit student info">
<p class="my-4">Hello from modal!</p>
</b-modal>
<b-button variant="danger" class="mx-1 p-1">
<b-icon-trash-fill></b-icon-trash-fill>
</b-button>
</div>
</template>
The problem here is most likely because your id's are not unique. For each row of your table you are generating the following;
<b-modal id="edit-student" title="Edit student info">
<p class="my-4">Hello from modal!</p>
</b-modal>
with the same ID.
I think the best approach here would be to include the Actions in the Table component. You don't need the extra component since it has no internal logic or props.
Table.vue
data() {
return {
fields: [
'index',
'full_name',
{ key: "date_of_birth", label: 'Date of Birth' },
'municipality',
{ key: "action", label: 'Action' }
],
lastClickedRowIndex: -1
}
},
...
<template>
<b-table :fields="fields" :items="tableItems" :per-page="perPage" :current-page="currentPage" responsive="sm" primary-key="index"
striped hover>
<template #cell(action)="data">
<b-button variant="primary" class="mx-1 p-1" #click="() => (lastClickedRowIndex = data.index)" v-b-modal.edit-student>
<b-icon-pencil-fill></b-icon-pencil-fill>
</b-button>
<b-button variant="danger" class="mx-1 p-1">
<b-icon-trash-fill></b-icon-trash-fill>
</b-button>
</template>
</b-table>
<b-modal id="edit-student" title="Edit student info">
<p class="my-4">Hello from modal!</p>
<!-- Dynamically change contents here based on lastClickedRowIndex -->
</b-modal>
</template>
I apologise if any syntax is incorrect I've only used Vue 3.
Related
How can I show a custom component- BaseButton.vue in the rows of the table, if table component and button are common components?
I made the component BaseTable.vue. I use this component for all tables.
<template>
<b-table :fields="fields" :items="items"></b-table>
</template>
<script>
export default {
props: {
fields:{
type: Array,
require: true
},
items:{
type: Array,
require: true
},
}
fields - object in a parent component.
items - object from API.
Parent component (base button doesn't appear in table on page):
<template>
<base-table :fields="fields" :items="items">
<template #cell(actions)>
<base-button></base-button>
</template>
<base-table>
</template>
<script>
import BaseButton from '...';
export default {
data() {
return {
fields: [
{ key: 'Caption', label: 'Name' },
{ key: 'PreviousMonthCounter', label: 'Prev. month' },
{ key: 'CurrentMonthCounter', label: 'Curr. month' },
{ key: 'actions', label: 'Action' }
],
items: []
}
},
components: {
BaseButton
}
}
And now I need to render BaseButton.vue in the last column of the table and I can't to make it - table has only text fields from items.
Need to create a child component (BaseTable):
<template>
<b-table :items="items" :fields="fields">
<slot v-for="slot in Object.keys($slots)" :name="slot" :slot="slot" />
<template v-for="slot in Object.keys($scopedSlots)" :slot="slot" slot-scope="scope">
<slot :name="slot" v-bind="scope"></slot>
</template>
</b-table>
</template>
And use it in the parent component like , but must be replaced by your component name.
For example:
<base-table :fields="fields" :items="items">
<template #cell(nameOfTheField)="{item}">
{{ item.key }}
</template>
<template #cell(nameOfTheField)>
<base-button></base-button>
</template>
i have code below and i want to navigate the user's profile when click on the name after asked by, i tried code below but it's not working and my json file structured as below
**
questions:Array[10] 0:Object category:"General Knowledge" user:"Saffron" difficulty:"medium" incorrect_answers:Array[3] 0:"Cinnamon" 1:"Cardamom" 2:"Vanilla" question:"What is the world's most expensive spice by weight?" type:"multiple"
**
<template>
<div class="container" width=800px>
<b-row>
<b-col cols="8">
<h1> Recently Asked </h1>
<ul class="container-question" v-for="(question1,index) in questions" :key="index"
>
<li v-if="answered" >
{{question1.question.selectedAnswer[index]}}
<li v-else >
{{question1.question}}
<b-row id="asked-info">
<p>Asked by: </p>
<div
id="user"
v-for="(answer, index) in answers(question1)"
:key="index"
>
<router-link to='/profile'> {{ answer }} </router-link>
</div>
</b-row>
<b-row>
<div class="category" v-for="(category,index) in category(question1)" :key="index" #click="selectedAnswer(index)">
<mark> {{ category }} </mark>
</div>
<b-button class="outline-primary" style="margin:auto;">Answer</b-button>
</b-row>
</li></ul>
</b-col>
<b-col>
<div class="ask-button">
<b-button href="#" class="primary">Ask Question</b-button>
</div>
<div>
<b-card
title="Card Title"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
Some quick example text to build on the card title and make up the bulk of the card's content.
</b-card-text>
</b-card>
</div>
<div>
<b-card
title="Card Title"
style="max-width: 20rem;"
class="mb-2"
>
<b-card-text>
Some quick example text to build on the card title and make up the bulk of the card's content.
</b-card-text>
</b-card>
</div>
</b-col>
</b-row>
<router-view />
</div>
</template>
<script>
export default {
data(){
return{
questions: [],
answered: null,
index: 0,
selectedIndex: null,
}
},
watch: {
question1: {
handler() {
this.selectedIndex = null;
this.answered = false;
},
},
},
methods: {
answers(question1) {
let answers = [question1.correct_answer];
return answers;
},
category(question1){
let category = [...question1.incorrect_answers];
return category
},
selectedAnswer(index) {
this.selectedIndex = index;
this.answered = true;
},
// filteredCategory(question1){
// return (question1.filter((question) => question.incorrect_answers == "Kfc"));
// },
},
mounted: function(){
fetch('https://opentdb.com/api.php?amount=10&category=9&difficulty=medium&type=multiple',{
method: 'get'
})
.then((response) => {
return response.json()
})
.then((jsonData) => {
this.questions = jsonData.results
})
}
}
</script>
routes.js
import question from './views/question.vue';
import App from './App.vue';
import Register from '#/components/Register'
import Login from '#/components/Login'
import Logout from '#/components/Logout'
import profile from '#/components/profile'
import contactus from '#/views/contactus'
export const routes = [
{ path: "/question", component: question },
{ path: "/", component: App },
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/register',
name: 'Register',
component: Register
},
{
path: '/logout',
name: 'Logout',
component: Logout
},
{
path: '/profile',
name: 'profile',
component: profile
},
{
path: '/contactus',
name: 'contactus',
component: contactus
},
]
There are a few things I want to point out here, you don't have to implement all of these, but understand what you are doing wrong:
You are missing the <router-view> tag, which is where you want your component to render because you use <router-link>
params are ignored if a path is provided. Instead, you need to provide the name of the route or manually specify the whole path with any parameter. Here for your reference: https://router.vuejs.org/guide/essentials/navigation.html
if you don't v-bind your property to, it will treat the value as a string. So you want to go like this <router-link :to="yourLink" />
In your case, you want to use <router-link :to="" /> with a dynamic string. There are 2 ways to achieve this: Declarative (<router-link>) and Programmatic (router.push)
Declarative:
<router-link> is like an anchor tag, so you should return a string.
<router-link :to="yourLink"> {{ userr }} </router-link>
computed: {
yourLink() {
return `/profile/${user(question1)}`
}
}
Programmatic
When you do a router.push, you are not doing a “link” anymore. It’s just a click event at that point. So change your router-link to a button with a click event
<button #click="yourLink"> {{ userr }} </button>
methods: {
yourLink() {
this.$router.push({name:'yourComponentName'} ,params:{user(question1)}})
}
}
EDIT
In your router.js:
{
path:'/profile/:user',
name: 'profile,
component: profile,
props: true
}
In your router-link:
<router-link :to="`/profile/${user(question1)}`"> {{ userr }} </router-link>
Then in your profile.vue, you can query the params by this.$route.params.user, for example, if you want to output it in a <p> tag:
<p>{{ this.$route.params.user }}</p>
How do I access the data entered in my input elements, which are passed through via a slot, to my child component that opens up a modal with the form elements inside of it?
I've been reading the vue docs about scoped slots but honestly, I just can't figure it out how to make it work in my example. None of the examples make use of an input element with a v-model that is being passed to the child component.
I have created a component "BaseFormModal" which contains the following code:
Note that the validation (vee-validate) occurs inside here, so this child component emits a "submit" event when the data is considered valid, which I then pick up in my parent component.
<template v-slot:default="slotProps">
<b-modal ref="base-form-modal" :title="title" :no-close-on-backdrop="true" #ok.prevent="onSubmit">
<validation-observer ref="observer" v-slot="{handleSubmit}">
<b-form ref="form" #submit.stop.prevent="handleSubmit(onSubmit)">
<slot />
</b-form>
</validation-observer>
</b-modal>
</template>
<script>
import { ValidationObserver } from 'vee-validate'
export default {
name: 'BaseFormModal',
components: {
ValidationObserver,
},
props: {
title: {
type: String,
required: true,
},
},
data () {
return {
formData: {},
}
},
methods: {
async onSubmit () {
let valid = await this.$refs.observer.validate()
if (!valid) {
return
}
this.$emit('submit', this.formData)
this.$nextTick(() => {
this.$refs['base-form-modal'].hide()
})
this.formData = {}
},
showModal () {
this.$refs['base-form-modal'].show()
},
},
}
</script>
<style lang="scss" scoped>
</style>
In my page, I have a button which opens up the modal, like so:
<b-button variant="primary" #click="$refs['addOrgUserModal'].showModal()">
<i class="far fa-plus" aria-hidden="true" /> {{ $t('organisation_settings_manage_users_add_user') }}
</b-button>
Then I have defined the base form modal component in my page as this:
<base-form-modal
ref="addOrgUserModal"
:title="$tU('organisation_settings_manage_users_add_user_modal_title')"
#submit="addOrgUser"
>
<b-row>
<b-col md="6">
<form-control-wrapper :rules="{required: true}" :label="$tU('first_name_label')">
<b-form-input
v-model="user.firstName"
type="text"
lazy-formatter
:formatter="trimSpaces"
:placeholder="$t('first_name_field_placeholder')"
/>
</form-control-wrapper>
</b-col>
<b-col md="6">
<form-control-wrapper :rules="{required: true}" :label="$tU('family_name_label')">
<b-form-input
v-model="user.familyName"
type="text"
lazy-formatter
:formatter="trimSpaces"
:placeholder="$t('family_name_field_placeholder')"
/>
</form-control-wrapper>
</b-col>
</b-row>
</base-form-modal>
I started using vue-bootstrap to generate a table with items. One of the columns "actions" contains button to show, edit and enable/disable the item (changing active property to true/false) based on item ID.
When I disable an item (click the button disable), the table is updated and does not show it anymore, however it looks like the buttons (show/edit/disable/enable) for that particular item stop working (e.g. not able to enable the project back or to edit it). Also the first item in the table does not have the buttons functional.
Item example:
{ id: '1', key: 'key1', name: 'name1', description: 'description1', active: 'true' }
Any idea what could be wrong?
<b-row class="mb-3">
<b-col cols="12">
<b-navbar type="light" variant="light">
<b-nav-form>
<i class="fas fa-search mr-3"></i>
<b-input-group>
<b-form-input v-model="filter" id="filterInput" placeholder="Search" ></b-form-input>
<b-input-group-append>
<b-button :disabled="!filter" #click="filter = ''" class="mr-sm-3">Clear</b-button>
</b-input-group-append>
</b-input-group>
<b-form-checkbox v-model="archivedChecked" size="sm">
Archived
</b-form-checkbox>
</b-nav-form>
</b-navbar>
</b-col>
</b-row>
<b-row>
<b-col cols="12">
<b-table bordered hover head-variant="dark" :filter="filter" :items="filteredProjects" :fields="fields">
<template v-slot:cell(actions)="row">
<b-button-group>
<b-button size="sm" variant="info" :to="'/msd/' + row.item.key" title="Show">Show</b-button>
<b-button v-b-modal="'edit-project-' + row.item.id" size="sm" variant="outline-info" :title="'Edit ' + row.item.name">
<i class="far fa-edit"></i>
</b-button>
<b-button v-if="!archivedChecked" v-b-modal="'disable-project-' + row.item.id" size="sm" variant="outline-secondary" title="Archive">
<i class="fas fa-archive"></i>
</b-button>
<b-button v-if="archivedChecked" v-b-modal="'enable-project-' + row.item.id" size="sm" variant="outline-secondary" title="Restore">
<i class="fas fa-trash-restore"></i>
</b-button>
<edit-project-modal :modal_id="'edit-project-' + row.item.id" :id="row.item.id" />
<disable-project-modal :modal_id="'disable-project-' + row.item.id" :id="row.item.id"/>
<enable-project-modal :modal_id="'enable-project-' + row.item.id" :id="row.item.id"/>
</b-button-group>
</template>
</b-table>
</b-col>
</b-row>
</div>
</template>
<script>
import EditProject from '~/components/modal/EditProject.vue'
import DisableProject from '~/components/modal/DisableProject.vue'
import EnableProject from '~/components/modal/EnableProject.vue'
import { mapGetters } from 'vuex'
export default {
layout: 'IncludeSidebar',
data() {
return {
fields: [
{
key: 'key',
sortable: false
},
{
key: 'name',
sortable: true
},
{
key: 'description',
sortable: true
},
{
key: 'actions',
sortable: false
}
],
archivedChecked: false,
filter: null
}
},
computed: {
loadedProjects() {
return this.$store.getters.loadedProjects;
},
filteredProjects() {
if (this.archivedChecked) {
return this.loadedProjects.filter(item => item.active === false)
} else {
return this.loadedProjects.filter(item => item.active === true)
}
}
},
methods: {
},
components: {
NewProjectModal: NewProject,
EditProjectModal: EditProject,
DisableProjectModal: DisableProject,
EnableProjectModal: EnableProject
}
}
</script>
Instead of calling the modals directly from buttons, I moved the trigger to a method and it solved the issue.
<b-button v-if="row.item.active" #click="showModal('disable-project-' + row.item.id)" size="sm" variant="outline-secondary" title="Archive">
methods: {
showModal(type, modal_id) {
this.$root.$emit('bv::show::modal', modal_id);
}
}
Why my b-table displays all columns of my table even I only selected some?
Here is my server-side code that calls the selected columns. Also, it worked if I use bootstrap only not bootstrap-vue.
router.get('/users', function(req, res) {
/* Get the person who has the latest date */
let getUser = "SELECT DISTINCT(MEMB.MEMB_N), MAX(PrintDate) AS PrintDate, MEMB.* \
FROM MEMB LEFT JOIN VD_Print ON MEMB.MEMB_N = VD_Print.MEMB_N GROUP BY MEMB.LAST_M \
ORDER BY PrintDate DESC LIMIT 100;"
myDB.query(getUser, function(err, rows) {
if (err) {
console.log(err);
} else {
console.log(rows);
res.send(rows);
}
});
});
And this one is on my client-side which is vuejs
<template>
<section>
<div class="sidebar"></div>
<div>
<b-form-input class="searchBar" placeholder="Search Here"></b-form-input>
</div>
<div>
<b-table class="table" striped hover :items="results"></b-table>
</div>
<b-button class="printBtn">PRINT</b-button>
</section>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
results: [],
};
},
mounted() {
this.getUsers();
},
methods: {
getUsers: function() {
axios
.get("http://localhost:9000/api/users/")
.then(response => (this.results = response.data))
.catch(error => alert(error));
}
}
};
</script>
My JSON looks like this:
You need to define the your column name in field definition of b-table. If you multiple filed in response but you want to display some fields.
Please below code and working demo.
CODE SNIPPET
export default {
data() {
return {
selectAll: false,
records: [],
perPage: 10,
currentPage: 1,
pageOptions: [5, 10, 15],
column: [{
key: "name",
sortable: true,
label: "Log File Name"
}, {
key: "lastModified",
sortable: true,
label: "Last Modified Date",
class: "text-right options-column"
}]
};
}
}
<template>
<div>
<div v-if="!hasRecords" style="text-align: center"><br><br>LOADING DATA...</div>
<div style="padding: 15px;" v-if="hasRecords">
<b-table :items="records" :fields="column" striped hover :current-page="currentPage" :per-page="perPage">
</b-table>
<b-row>
<b-col md="6" class="my-1">
<b-pagination :total-rows="totalRows" :per-page="perPage" v-model="currentPage" class="my-0" />
</b-col>
<b-col md="6" class="my-1">
<b-form-group horizontal label="Per page" class="mb-0">
<b-form-select :options="pageOptions" v-model="perPage" />
</b-form-group>
</b-col>
</b-row>
</div>
</div>
</template>