Adding the column name in the table and links in vue - javascript

I am beginner web developer. I make my first crud in Laravel 8 and Vue.
I use this component t in my project: https://www.npmjs.com/package/vuejs-datatable
I have this code:
DataTable.vue:
<template>
<div>
<div class="row mb-3">
<div class="col-3">
<div class="input-group">
<input
v-model="search"
class="form-control"
placeholder="Szukaj..."
type="text"
>
<div class="input-group-append">
<button class="btn btn-primary" type="button" #click.prevent="handleSearch">
<font-awesome-icon icon="fas fa-search" />
</button>
</div>
</div>
</div>
<div class="col-2">
<div class="input-group">
<label for="pageOption" class="mt-2 mr-2">Na stronie</label>
<select class="form-control" v-model="perPage" #change="handlePerPage" id="pageOption">
<option v-for="page in pageOptions" :key="page" :value="page">{{ page }}</option>
</select>
</div>
</div>
</div>
<table class="table table-hover">
<thead>
<tr>
<th class="table-head">#</th>
<th v-for="column in columns" :key="column" #click="sortByColumn(column)"
class="table-head">
{{ column | columnHead }}
<span v-if="column === sortedColumn">
<font-awesome-icon v-if="order === 'asc' " icon="fas fa-angle-up" />
<font-awesome-icon v-else icon="fas fa-angle-down" />
</span>
</th>
</tr>
</thead>
<tbody>
<tr class="" v-if="tableData.length === 0">
<td class="lead text-center" :colspan="columns.length + 1">Brak danych do wyświetlenia.</td>
</tr>
<tr v-for="(data, key1) in tableData" :key="data.id" class="m-datatable__row" v-else>
<td>{{ serialNumber(key1) }}</td>
<td v-for="(value, key) in data">{{ value }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script type="text/ecmascript-6">
import axios from 'axios';
import Vue from 'vue';
import 'vuejs-datatable/dist/themes/bootstrap-4.esm';
import {
VuejsDatatableFactory,
IDataFnParams,
IDisplayHandlerParam,
ITableContentParam,
TColumnsDefinition,
VueDatatable
} from 'vuejs-datatable';
Vue.use(VuejsDatatableFactory, VueDatatable);
export default {
props: {
fetchUrl: {type: String, required: true},
columns: {type: Array, required: true},
},
data() {
return {
tableData: [],
url: '',
pagination: {
meta: {to: 1, from: 1}
},
offset: 4,
currentPage: 1,
perPage: 1,
sortedColumn: this.columns[0],
order: 'asc',
search: null,
pageOptions: [1, 10, 20, 50],
}
},
watch: {
fetchUrl: {
handler: function (fetchUrl) {
this.url = fetchUrl
},
immediate: true
}
},
created() {
return this.fetchData()
},
computed: {
/**
* Get the pages number array for displaying in the pagination.
* */
pagesNumber() {
if (!this.pagination.meta.to) {
return []
}
let from = this.pagination.meta.current_page - this.offset
if (from < 1) {
from = 1
}
let to = from + (this.offset * 2)
if (to >= this.pagination.meta.last_page) {
to = this.pagination.meta.last_page
}
let pagesArray = []
for (let page = from; page <= to; page++) {
pagesArray.push(page)
}
return pagesArray
},
/**
* Get the total data displayed in the current page.
* */
totalData() {
return (this.pagination.meta.to - this.pagination.meta.from) + 1
}
},
methods: {
fetchData() {
let dataFetchUrl = `${this.url}&page=${this.currentPage}&column=${this.sortedColumn}&order=${this.order}&per_page=${this.perPage}`
axios.get(dataFetchUrl)
.then(({data}) => {
this.pagination = data
this.tableData = data.data
}).catch(error => this.tableData = [])
},
/**
* Get the serial number.
* #param key
* */
serialNumber(key) {
return (this.currentPage - 1) * this.perPage + 1 + key
},
/**
* Change the page.
* #param pageNumber
*/
changePage(pageNumber) {
this.currentPage = pageNumber
this.fetchData()
},
/**
* Sort the data by column.
* */
sortByColumn(column) {
if (column === this.sortedColumn) {
this.order = (this.order === 'asc') ? 'desc' : 'asc'
} else {
this.sortedColumn = column
this.order = 'asc'
}
this.fetchData()
},
handleSearch() {
this.fetchData()
},
handlePerPage($event) {
this.perPage = $event.target.value;
this.fetchData()
}
},
filters: {
columnHead(value) {
return value.split('_').join(' ').toUpperCase()
}
},
translate: {
nextButton: 'Dalej',
previousButton: 'Wstecz',
placeholderSearch: 'Szukaj...',
},
name: 'DataTable'
}
</script>
<style scoped>
</style>
Notes.vue:
<template>
<CRow>
<CCol col="12">
<transition name="slide">
<CCard>
<CCardBody>
<h4>
Menus
</h4>
<CButton color="success" #click="createNote()" class="mb-3 my-5">Add Menu <font-awesome-icon icon="fas fa-plus" /> </CButton>
<div class="flex-center position-ref full-height">
<data-table
:fetch-url="datatTableUrl"
:columns="['name', 'email', 'id' , 'created_at']"
:headers="['nazwa', 'adres email', 'ID' , 'utworzono']"
></data-table>
</div>
</CCardBody>
</CCard>
</transition>
</CCol>
</CRow>
</template>
<script>
import Vue from 'vue';
export default {
data() {
return {
datatTableUrl: '',
}
},
created: function () {
this.datatTableUrl = Vue.prototype.$apiAdress + '/api/users/dataTable' + '?token=' + localStorage.getItem("api_token");
},
methods: {
noteLink(id) {
return `notes/${id.toString()}`
},
editLink(id) {
return `notes/${id.toString()}/edit`
},
showNote(id) {
const noteLink = this.noteLink(id);
this.$router.push({path: noteLink});
},
editNote(id) {
const editLink = this.editLink(id);
this.$router.push({path: editLink});
},
deleteNote(id) {
let self = this;
let noteId = id;
axios.post(this.$apiAdress + '/api/notes/' + id + '?token=' + localStorage.getItem("api_token"), {
_method: 'DELETE'
})
.then(function (response) {
self.message = 'Successfully deleted note.';
self.showAlert();
self.getNotes();
}).catch(function (error) {
console.log(error);
self.$router.push({path: '/login'});
});
},
createNote() {
this.$router.push({path: 'notes/create'});
},
}
}
</script>
This code work fine.
I have 2 problems:
I would like the column headers in the table to be taken from: headers - currently these are the column names from the database (ie: columns).
I would like to add a link to edit and delete a record. I have created methods: editLink (), deleteNote (). How can I add them to my table? I would like them to be visible next to the "name" column
How can I make it?
Please help me :)

For the problem #1. I would do it this way.
Merge the columns and the headers as one Object, ex: where the key will be the column name (Important: don't forget to register the headers prop).
<data-table
:fetch-url="datatTableUrl"
:headers="{'name': 'nazwa','email': 'adres email','id': 'ID' , 'created_at': 'utworzono'}"
></data-table>
In the DataTable:
<th v-for="(column, label) in headers" :key="column" #click="sortByColumn(column)" class="table-head">
{{ label | columnHead }}
<span v-if="column === sortedColumn">
<font-awesome-icon v-if="order === 'asc' " icon="fas fa-angle-up" />
<font-awesome-icon v-else icon="fas fa-angle-down" />
</span>
</th>
For the Second problem #2 (Not very clear), Better to move these functions to the DataTable and add the actions as a new column, short example:
<td><button #click="editLink(key1)">Edit link or some fa fa icon</button></td>
Add as prop in the DataTable:
props: {
fetchUrl: {type: String, required: true},
columns: {type: Array, required: true},
headers: {type: Object, required: true} //<-- This
},

Related

vuetify v-radio doesn't select one radio in table nuxt js 3

I try to make a multiple grid radio with conditions: if one radio button is selected, save it as json with key: question and value: column where the radio button is selected.
<template>
<v-radio-group class="mt-0"
v-model="answer"
:rules="answerRule"
>
<thead>
<tr>
<th>Pilihan</th>
<th v-for="(option, keys) in columns" :key="keys + 'A'">
{{ option.value }}
</th>
</tr>
<tr v-for="(option, keys) in rows" :key="keys + 'A'">
<th>
{{ option.value }}
</th>
<td v-for="(option, key) in columns" :key="key + 'B'">
<v-radio-group
v-model="answer"
#change="update"
:rules="answerRule"
>
<v-radio
solo
:key="key"
:value="option.value"
>
</v-radio>
</v-radio-group>
</td>
</tr>
</thead>
<tbody></tbody>
</v-radio-group>
</template>
Here is my script on how to load the data and try to save the json:
<script>
export default {
props: ['question'],
data() {
return {
rows: this.question.options,
answer: [],
columns: this.question.optionsColumns,
answerRule: [],
}
},
methods: {
async update() {
try {
let payload = {
questionId: this.question.id,
value: this.answer,
questionName: this.question.question,
}
//update question options
await this.$store.commit('answers/update', payload)
} catch (err) {
this.$store.commit('alerts/show', {
type: 'error',
message: err.response
? this.$t(err.response.data.message)
: this.$t('SERVER_ERROR'),
})
}
},
},
beforeMount() {
if (this.question.required) {
this.answerRule.push(
(v) => v.length > 0 || this.$t('QUESTION_REQUIRED')
)
}
},
}
</script>

How to create a vue modal that sends user input to another component

I am trying to create a modal component that takes in user input, and upon saving that information, is displayed within another component. For example, a user is prompted to input their first and last name respectively in a modal component (Modal.vue). Once the user saves that data (a submit method on the modal), the data is displayed on another component (InputItem.vue).
Currently, I have a CreateEvent.vue component that houses the input elements, a modal.vue component that is the modal, an EventItem.vue component, that will display what is entered on once CreateEvent is executed, an EventsList.vue component that displays all the events that a user creates and finally, app.vue which houses the Modal and Events components.
I have been able to successfully get this CRUD functionality working without the modal component, but once I add the modal, I am getting confused.
If you could help lead me in the right direction, I would appreciate that!
Modal.vue
<template>
<transition name="modal-fade">
<div class="modal-backdrop">
<div
class="modal"
role="dialog"
aria-labelledby="modalTitle"
aria-describedby="modalDescription"
>
<header class="modal-header" id="modalTitle">
<slot name="header">
Create an Event
<button
type="button"
class="btn-close"
#click="close"
aria-label="Close modal"
>
x
</button>
</slot>
</header>
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input
type="text"
placeholder="add an emoji?"
v-model="newEmoji"
/>
</div>
<button #click="doneEdit">Create Event</button>
<button #click="cancelEdit">Cancel</button>
</slot>
</section>
<footer class="modal-footer">
<slot name="footer">
I'm the default footer!
<button
type="button"
class="btn-green"
#click="close"
aria-label="Close modal"
>
Close me!
</button>
</slot>
</footer>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'modal',
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
close() {
this.$emit('close')
},
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
CreateEvent.vue
<template>
<div #keyup.enter="addTodo">
<input
type="text"
class="todo-input"
placeholder="What needs to be done"
v-model="newTodo"
/>
<input type="text" placeholder="add an emoji?" v-model="newEmoji" />
</div>
</template>
<script>
export default {
props: {
todos: {
type: Array
}
},
data() {
return {
newTodo: '',
newEmoji: '',
idForTodo: this.todos.length + 1
}
},
methods: {
addTodo() {
if (this.newTodo.trim().length == 0) return
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
this.newTodo = ''
this.newEmoji = ''
this.idForTodo++
}
}
}
</script>
EventItem.vue
<template>
<div class="todo-item">
<h3 class="todo-item--left">
<!-- <span v-if="!editing" #click="editTodo" class="todo-item--label">
{{ title }}
{{ emoji }}
</span> -->
<input
class="todo-item--edit"
type="text"
v-model="title"
#click="editTitle"
#blur="doneEdit"
/>
<input
class="todo-item--edit"
type="text"
v-model="emoji"
#click="editEmoji"
#blur="doneEdit"
/>
<!-- <button #click="doneEdit">Update</button>
<button #click="cancelEdit">Cancel</button> -->
</h3>
<button class="remove-item" #click="removeTodo(todo.id)">✘</button>
</div>
</template>
<script>
export default {
name: 'todo-item',
props: {
todo: {
type: Object,
required: true
}
},
data() {
return {
id: this.todo.id,
title: this.todo.title,
emoji: this.todo.emoji,
editing: this.todo.editing,
beforeEditCacheTitle: this.todo.title,
beforeEditCacheEmoji: this.todo.emoji
}
},
methods: {
editTitle() {
this.beforeEditCacheTitle = this.title
this.editing = true
},
editEmoji() {
this.beforeEditCacheEmoji = this.emoji
this.editing = true
},
doneEdit() {
if (this.title.trim() == '') {
this.title = this.beforeEditCacheTitle
}
if (this.emoji.trim() == '') {
this.emoji = this.beforeEditCacheEmoji
}
this.editing = false
this.$emit('finishedEdit', {
id: this.id,
title: this.title,
emoji: this.emoji,
editing: this.editing
})
},
cancelEdit() {
this.title = this.beforeEditCacheTitle
this.emoji = this.beforeEditCacheEmoji
this.editing = false
},
removeTodo(id) {
this.$emit('removedTodo', id)
}
}
}
</script>
Events.vue
<template>
<div>
<transition-group
name="fade"
enter-active-class="animated fadeInUp"
leave-active-class="animated fadeOutDown"
>
<EventItem
v-for="todo in todosFiltered"
:key="todo.id"
:todo="todo"
#removedTodo="removeTodo"
#finishedEdit="finishedEdit"
/>
</transition-group>
</div>
</template>
<script>
import EventItem from '#/components/EventItem'
export default {
components: {
EventItem
},
data() {
return {
filter: 'all',
todos: [
{
id: 1,
title: 'Eat sushi',
emoji: '💵',
editing: false
},
{
id: 2,
title: 'Take over world',
emoji: '👨🏽‍💻',
editing: false
}
]
}
},
computed: {
todosFiltered() {
if (this.filter == 'all') {
return this.todos
}
}
},
methods: {
removeTodo(id) {
const index = this.todos.findIndex(item => item.id == id)
this.todos.splice(index, 1)
},
finishedEdit(data) {
const index = this.todos.findIndex(item => item.id == data.id)
this.todos.splice(index, 1, data)
}
}
}
</script>
app.vue
<template>
<div id="app" class="container">
<button type="button" class="btn" #click="showModal">
Create Event
</button>
<Modal v-show="isModalVisible" #close="closeModal" />
<Events />
</div>
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
isModalVisible: false
}
},
methods: {
showModal() {
this.isModalVisible = true
},
closeModal() {
this.isModalVisible = false
}
}
}
</script>
The modal component should emit the values instead of pushing it into the todos array. When it emits it, the parent component (App.vue) listens for the emitted items.
I would do something like this
Modal.vue
<template>
...
// header
<section class="modal-body" id="modalDescription">
<slot name="body">
<div #keyup.enter="addTodo">
...
</div>
<button #click="handleModalSubmit">Create Event</button>
...
//footer
...
</template>
<script>
export default {
...
data() {
...
},
methods: {
...,
handleModalSubmit() {
this.$emit('todos-have-been-submitted', this.todos);
},
addTodo() {
...
this.todos.push({
id: this.idForTodo,
title: this.newTodo,
emoji: this.newEmoji
})
...
}
}
}
</script>
App.vue
<template>
...
<Modal
#todos-have-been-submitted="handleTodoSubmission" //watch the 'todos-have-been-submitted" emission and trigger handleTodoSubmission method when the emission is detected
/>
<Events
:todos="todos" // pass todos as a prop to the Events component
/>
...
</template>
<script>
import Events from './components/Events'
import Modal from './components/Modal'
export default {
name: 'App',
components: {
Events,
Modal
},
data() {
return {
...,
todos: []
}
},
methods: {
...,
handleTodoSubmission(todos) {
this.todos = [...todos];
}
}
}
</script>

How to pass the second variable to the Vue Laravel component and set it by default to the input variable?

I am starting to learn Vue. Please tell me what my code should look like when I need to pass 2 value to my component?
<div id="js-autocomplete-region">
<autocomplete-region-component :query-prop="{{ json_encode(old('regionName', $advert->region->name)) }}"></autocomplete-region-component>
</div>
in addition to $advert->region->name, I need to pass another $advert->region->id
this my code component vue
<template>
<div>
<input
type="text"
autocomplete="off"
v-model="query"
v-on:keyup="autoComplete"
class="form-control js-region-name"
name="regionName"
value=""
>
<input
type="hidden"
class="form-control js-region-id"
name="regionId"
value="">
<div class="panel-footer" v-if="results.length">
<ul class="list-group select-region">
<li class="list-group-item list-region" v-for="result in results" v-on:click="selectRegion(result)">
{{ result.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: {
queryProp: {
required: false,
type: String
}
},
data() {
return {
results: [],
query: this.queryProp
};
},
methods: {
autoComplete() {
this.results = [];
if(this.query.length > 2){
axios.get('/api/regions',{params: {_limit: 2, query: this.query}}).then(response => {
this.results = response.data;
});
}
},
selectRegion(result) {
let inputWithRegionName = document.querySelector('.js-region-name');
let inputWithRegionId = document.querySelector('.js-region-id');
let listRegions = document.querySelector('.panel-footer');
inputWithRegionName.value = result.name;
inputWithRegionId.value = result.id;
listRegions.hidden = true;
}
}
}
</script>
if there is $advert->region->id then it should be set here
update
<autocomplete-region-component
:query-prop="{{ json_encode(old('regionName', $advert->region->name)) }}"
other-value="{{ $advert->region ? $advert->region->id : '' }}"
#other-input="{{ $advert->region ? $advert->region->id : '' }}"
></autocomplete-region-component>
updating for component and screenshot
<template>
<div>
<input
type="text"
placeholder="Начните вводить ваш город и выберите его из списка"
autocomplete="off"
v-model="query"
v-on:keyup="autoComplete"
class="form-control js-region-name"
name="regionName"
value=""
>
<input
type="hidden"
class="form-control js-region-id"
name="regionId"
value="">
<div class="panel-footer" v-if="results.length">
<ul class="list-group select-region">
<li class="list-group-item list-region" v-for="result in results" v-on:click="selectRegion(result)">
{{ result.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: {
queryProp: {
required: false,
type: String
}
},
computed: {
val2: {
get() {
return this.otherValue;
},
set(newValue) {
this.$emit('other-input', newValue);
},
},
},
data() {
return {
results: [],
query: this.queryProp
};
},
methods: {
autoComplete() {
this.results = [];
if(this.query.length > 2){
axios.get('/api/regions',{params: {_limit: 2, query: this.query}}).then(response => {
this.results = response.data;
});
}
},
selectRegion(result) {
let inputWithRegionName = document.querySelector('.js-region-name');
let inputWithRegionId = document.querySelector('.js-region-id');
let listRegions = document.querySelector('.panel-footer');
inputWithRegionName.value = result.name;
inputWithRegionId.value = result.id;
listRegions.hidden = true;
}
}
}
</script>
Vlad, nearly there, you pass the second prop just as you did with the first one. You should then bind (v-model) the input to this value, however doing jus that will cause an error (avoid mutating a prop),So use as prop, maybe, initRegion, the data variable you maybe call region and set it to this.initRegion. If you struggle, let me know.
<html>
<head>
<meta charset="UTF-8">
<title>Vue Testing</title>
</head>
<body>
<div id="app">
<autocomplete-region-component
query-prop="regionName"
init-region="1"
>
</autocomplete-region-component>
</div>
<!-- development version, includes helpful console warnings -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('autocomplete-region-component', {
props: {
queryProp: {
required: false,
type: String
},
initRegion: {
required: false,
type: String
}
},
computed: {
val2: {
get() {
return this.otherValue;
},
set(newValue) {
this.$emit('other-input', newValue);
},
},
},
data() {
return {
results: [],
query: this.queryProp,
region: this.initRegion
};
},
methods: {
autoComplete() {
this.results = [];
if(this.query.length > 2){
axios.get('/api/regions',{params: {_limit: 2, query: this.query}}).then(response => {
this.results = response.data;
});
}
},
selectRegion(result) {
let inputWithRegionName = document.querySelector('.js-region-name');
let inputWithRegionId = document.querySelector('.js-region-id');
let listRegions = document.querySelector('.panel-footer');
inputWithRegionName.value = result.name;
inputWithRegionId.value = result.id;
listRegions.hidden = true;
}
},
template:`
<div>
<input
type="text"
placeholder="Начните вводить ваш город и выберите его из списка"
autocomplete="off"
v-model="query"
v-on:keyup="autoComplete"
class="form-control js-region-name"
name="regionName"
value=""
>
<input
type="hidden"
class="form-control js-region-id"
name="regionId"
:value="region">
<div class="panel-footer" v-if="results.length">
<ul class="list-group select-region">
<li class="list-group-item list-region" v-for="result in results" v-on:click="selectRegion(result)">
{{ result.name }}
</li>
</ul>
</div>
</div>
`,
mounted() {
//
}
})
var app = new Vue({
el: '#app',
data: {},
})
</script>
</body>
</html>
As the input is hidden, I pressume you don't care to change the id further. In that case actually you don't even have to use it as a data property.
You can pass as many values as you want via props, however there is only one "value" that will interact with v-model.
The good news is that v-model is really just a handy abstracted pattern for components.
You could make a second "value" in a component as follows:
<!-- updateV2 method sets v2 to value raised in the event -->
<my-component
v-model="v1"
other-value="v2"
#other-input="updateV2"
></my-component>
computed: {
val2() {
get() {
return this.otherValue;
},
set(newValue) {
this.$emit('other-input', newValue);
},
},
}

Display previously uploaded files or images

I have two data tables and am using Django at the back end and Vuejs at the front end.
(1) Products, stores the product details and a single image.
(2) Products_Images, stores the multiple images with relation to the product.
The below code is called from < Product List > when Editing the product details including adding images or removing images.
My problem is that in Edit mode I need the previously selected displayed instead of ( No Files Chosen )
<!-- Edit a Product (Start) -->
<template id="product-edit">
<div>
<h2>Product (Edit)</h2>
<form method="post" enctype="multipart/form-data" ref="itemMaster">
<!-- Display Product Name -->
<div class="form-group">
<input class="form-control" id="edit-name" v-model="product.itemfullhd" required/>
</div>
<!-- Upload Single Image -->
<div class="form-group">
<!-- ### MY PROBLEM HERE ### -->
<label for="edit-imagemain">Main Image </label>
<input type="file" id="edit-imagemain" v-model="product.Image_file" #change="onFileChanged" required/>
<img class="cart-thumbnail" v-bind:src="'/media/' + product.image_file" alt="image"/>
</div>
<!-- Upload Multiple Images -->
<div class="form-group">
<!-- ### MY PROBLEM ALSO HERE ### -->
<label for="files">Xtra Images</label>
<input type="file" id="files" ref="files" multiple v-on:change="handleFilesUpload()"/>
<div>
<!-- Display the Multiple Images -->
<table class="table table-striped ">
<thead>
<tr>
<th>Xtra Image File/s</th>
<th>Image</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="imagerec in products_images" and v-if="( imagerec.itemfullhd == product.itemfullhd )" style="clear: both;">
<td>/media/{{imagerec.images_multiple}}</td>
<td> <img class="cart-thumbnail" v-bind:src="'/media/' + imagerec.images_multiple" alt="image"/> </td>
<td> <input type="checkbox" :value="imagerec.mark" number> </td>
</tr>
</tbody>
</table>
</div>
</div>
<button type="submit" class="btn btn-primary" #click.prevent="updateProduct">Save</button>
<a class="btn btn-dark"><router-link to="/product-list">Cancel</router-link></a>
</form>
</div>
</template>
<!-- Edit a Product (Done) -->
<script>
var store = new Vuex.Store({
state: {
products: [],
products_images: [],
},
mutations: {
loadProducts: (state, {products, products_images}) => {
state.products = products;
state.products_images = products_images;
},
},
actions: {
retrieveProducts: ({ commit }) => {
axios.get('/biggmount_home/Show-Items-Axios')
.then(response => {
products = response.data.md_items_qs2; // send by views.py
products_images = response.data.md_items_images_qs2; // send by views.py
commit('loadProducts', {products, products_images})
})
.catch(error => {
alert("ERROR")
commit('ERRORED', error)
})
// AXIOS method - End
},
},
})
function findProduct (productId) {
return products[findProductKey(productId)];
};
function findProductKey (productId) {
for (var key = 0; key < products.length; key++) {
if (products[key].id == productId) {
return key;
}
}
};
var ProductEdit = Vue.extend({
template: '#product-edit',
data: function () {
return {
product: findProduct(this.$route.params.product_id),
selectedImage: null,
// image_file: product.image_file,
// files: products_images.images_multiple,
selected: [],
};
},
methods: {
onFileChanged (event) {
this.selectedImage = event.target.files[0]
this.product.image_file = this.selectedImage.name
},
/* Handles a change on the file upload */
handleFilesUpload(){
this.files = this.$refs.files.files;
this.image_file = this.$refs.files.files;
},
updateProduct: function () {
let product = this.product;
const formData = new FormData()
if (this.selectedImage != null) {
formData.append('Item Image', this.selectedImage, this.selectedImage.name)
} else {
formData.append('Item Image', [])
}
if (this.files != null) {
/* Iterate over any file sent over appending the files to the form data. */
for( var i = 0; i < this.files.length; i++ ){
let file = this.files[i];
formData.append('Item Images', file);
}
} else {
formData.append('Item Images', [])
}
formData.append('Item Id', product.id)
formData.append('Item Name', product.itemfullhd)
formData.append('Item Imagefl', product.imagefl)
axios.post('/biggmount_home/Post-Items-Axios', formData)
products[findProductKey(product.id)] = {
id: product.id,
itemfullhd: product.itemfullhd,
imagefl: product.imagefl,
image_file: '/biggmount_shop/images/' + this.selectedImage.name,
std_rte: product.std_rte,
};
router.push('/product-list');
},
}
});
const app = new Vue({
router,
store,
methods: {
...Vuex.mapMutations([
'loadProducts',
]),
...Vuex.mapActions([
'retrieveProducts'
]),
},
computed: {
...Vuex.mapState([
'products',
'products_images',
]),
},
mounted() {
this.retrieveProducts()
},
}).$mount('#app')
</script>

Data value not updated with tabs in Vue js

Hi guys Im trying to make my custom tabs, with Vue js but Im having a problem since like my data property is not getting updated :(...
Here is the case Im having trouble with:
When I open 3 tabs, If I open my Modal on the first tab and then close that first tab, I will be switched to second tab but my Modal that was from first tab stays open like it is modal from the first tab instead of second... I would like each tab to have its own modal instance.
Here I posted bellow gif of what is happening. Basically I dont want my modal to apear again on next tab, when previous is closed :)
Seems like my data values, are not destroyed with first tab, and they are just replicated onto the second tab, Im trying to figure out what is the issue for few days now but no succes...
Here is my App.vue
<template>
<div id="app">
<div class="event-tabs wrapper">
<div class="is-flex">
<div class="tabs is-boxed control">
<ul>
<li v-for="(newEvent, index) in newEventList" :key="index" :class="selectedEventClass(index)"
#click.left="selectEvent(index)" #click.middle="discardEvent(index)">
<span class="event-tab-title">
TAB
</span>
<span class="event-tab-close" #click.stop="closeEvent(index)">
<i class="fa fa-times"></i>
</span>
</li>
<li class="add-tab">
<a #click.prevent="createEvent" :title="'Create Tab'">
<span>+</span>
</a>
</li>
</ul>
</div>
</div>
<div class="tab-content">
<tab v-for="(event, index) in newEventList" :event="event" :index="index"
v-if="showEventTab" v-show="index === selectedEvent" :key="index"
ref="eventTab"></tab>
</div>
</div>
</div>
</template>
<script>
import tab from './components/EventTab.vue';
export default {
name: 'app',
components: {
tab,
},
computed: {
newEventList() {
return this.$store.getters['eventModule/getNewList'];
},
selectedEvent() {
return this.$store.getters['eventModule/getSelectedNew'];
},
eventToEdit() {
return this.$store.state.event.eventToEdit;
},
showEventTab() {
return this.newEventList.length > 0;
},
},
methods: {
selectedEventClass(eventIndex) {
return (eventIndex === this.selectedEvent) ? 'is-active' : '';
},
createEvent() {
this.$store.dispatch('eventModule/create');
},
selectEvent(eventIndex) {
this.$store.dispatch('eventModule/select', { eventIndex });
},
closeEvent(eventIndex) {
this.$store.dispatch('eventModule/close', { eventIndex });
},
},
}
</script>
<style lang="scss">
#import './assets/scss/main';
</style>
My Tab component:
<template>
<div class="event-form" v-if="event">
<div class="columns">
<div class="column is-half">
<div class="field">
<h1>This is the TAB number {{ index}} </h1>
</div>
<p class="control">
<button class="button is-danger" #click.prevent="openDialog">
Open Dialog
</button>
</p>
<modalDialog type="none" :show="modal.show"
:className="'eventTabModal'" :title="'Test modal'"
:text="'Test modal'"
#hide="closeDiscardModal">
<h3>Modal is active</h3>
</modalDialog>
</div>
</div>
</div>
</template>
<script>
import modalDialog from './ModalDialog.vue';
export default {
components: {
modalDialog,
},
props: ['event', 'index'],
data() {
return {
eventDefault: {},
/**
* Discard event modal
*/
modal: {
show: false,
},
};
},
computed: {
eventList() {
return this.$store.getters['event/getNewList'];
},
eventTypeList() {
return this.$store.getters['eventType/getList'];
},
},
methods: {
/**
* Opens discarded Modal
*/
closeDiscardModal() {
this.modal = {
show: false,
};
},
openDialog() {
this.modal = {
show: true,
};
},
},
}
</script>
My Modal component for displaying Dialog:
<template>
<transition name="fade">
<div class="modal is-active" v-show="shouldShowModal" :class="className">
<div class="modal-background" #click="hideModal"></div>
<div class="modal-card">
<header class="modal-card-head" v-if="title">
<p class="modal-card-title">{{ title }}</p>
</header>
<section class="modal-card-body">
<slot>
{{ text }}
</slot>
</section>
<footer class="modal-card-foot" v-if="type !== 'none'">
<template v-if="type === 'confirm'">
<a class="button is-success" #click.prevent="buttonClicked('yes')">Yes</a>
<a class="button is-danger" #click.prevent="buttonClicked('no')">No</a>
</template>
<template v-else-if="type === 'info'">
<a class="button" #click.prevent="buttonClicked('ok')">Ok</a>
</template>
</footer>
</div>
<button class="modal-close is-large" #click="hideModal"></button>
</div>
</transition>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false,
},
title: {
type: String,
default: '',
},
text: {
type: String,
default: '',
},
type: {
type: String,
default: 'info',
},
className: {
type: String,
default: '',
},
},
data() {
return {
shouldShowModal: this.show,
};
},
watch: {
show(newValue) {
this.shouldShowModal = newValue;
},
},
methods: {
hideModal() {
this.shouldShowModal = false;
this.$emit('hide');
},
buttonClicked(type) {
this.hideModal();
this.$emit('buttonClicked', type);
},
},
};
</script>
And My store module for Tabs:
const eventModule = {
namespaced: true,
state: {
/**
* List of opened tabs
*/
newList: [],
selectedNew: 0,
savedList: [],
eventToEdit: null,
},
getters: {
getNewList(state) {
return state.newList;
},
getSelectedNew(state) {
return state.selectedNew;
},
getSavedList(state) {
return state.savedList;
},
},
mutations: {
addNew(state, { location } = {}) {
state.newList.push({
typeId: null,
active: true,
logs: [],
});
},
removeNew(state, index) {
state.newList.splice(index, 1);
},
setNew(state, { index = state.selectedNew, event }) {
state.newList.splice(index, 1, event);
},
selectNew(state, selectedNew) {
state.selectedNew = selectedNew;
},
},
actions: {
/**
* opens tab for creating new event
*
* #param context
* #param location
* #param stopProp
* #returns {*}
*/
create(context, { location, stopProp } = {}) {
const newList = context.getters.getNewList;
context.commit('addNew', { location });
context.commit('selectNew', newList.length - 1);
// if (!stopProp) {
// context.dispatch('stateChanged', null, { root: true });
// }
return Promise.resolve();
},
/**
* Saves event
* #param context
* #param event
* #return {Promise|Promise.<TResult>}
*/
save(context, { event, index, hideMessage }) {
const method = (event.id) ? 'patch' : 'post';
// const data = { event, userId: context.rootGetters['user/getData'].id };
const data = { event };
const payload = { method, url: 'event', data, hideMessage };
return context.dispatch('server/http', payload, { root: true })
.then((response) => {
context.commit('setNew', { event: response.data.object, index });
context.dispatch('loadList');
})
.catch(error => Promise.reject(error));
},
select(context, { eventIndex, stopProp }) {
context.commit('selectNew', eventIndex);
},
opened(context) {
const event = JSON.parse(JSON.stringify(context.state.eventToEdit));
context.state.eventToEdit = null;
context.dispatch('create', { stopProp: true });
context.commit('setNew', { event });
},
/**
* Closes for event
* #param context
* #param eventIndex
* #param stopProp
* #return {Promise|Promise.<TResult>}
*/
close(context, { eventIndex, stopProp }) {
const newList = context.getters.getNewList;
const selectedNew = context.getters.getSelectedNew;
context.commit('removeNew', eventIndex);
if (selectedNew >= newList.length && selectedNew > 0) {
context.commit('selectNew', selectedNew - 1);
}
},
},
};
export default eventModule;
Also Here is the link to my github page where full test code is located if someone wants to take a look:
Codesandbox link
Thanx in advance.
Solved it. The problem is with keys in v-for, :key prop, should be unique, so here is how I solved it, in mutations addNew, add new property tabId add like this:
state.newList.push({
tabId: new Date.now(),
typeId: null,
active: true,
briefing: false,
logs: [],
});
and App.vue change :key=“index” to :key=“event.tabId”

Categories

Resources