I’m making a dynamic admin panel with crud generator with the help of laravel and vue.
I have a table where I’m loading data asynchronously from API. There is an is_featured column in my table which I want to be a switch. So that the user can change the value from the table page instead of going to edit page.
To generate my entire table there is a configuration object that contains which fields to show and the type of that field and other metadata. In the configuration object, there is a field named prerender
which is responsible to prerender fields that require calling other API or some editable fields like select dropdown or switch.
To make switch and select fields work, I have an empty object named fieldData
.
When a field with type: boolean
and prerender: true
is found, my code will initialize a property with field.name as property name in field data and fill it with the corresponding values under that field name
this.fieldData[field.name] = {}; this.tableData.forEach( data => (this.fieldData[field.name][data.id] = data[field.name]) );
BUT THE SWITCH AND SELECT ARE NOT WORKING HERE
So I need help.
Here’s my entire code for reference
<template> <div class="app-container"> <el-row :gutter="20" style="display: flex; align-items: center;"> <el-col :span="10"> <h1 style="text-transform: capatilize;">{{ resourceName }}</h1> </el-col> <el-col :span="14" style="display: flex; justify-content: flex-end; align-items: center"> <el-input v-model="navigation.search" placeholder="Search anything here" prefix-icon="el-icon-search" style="width: 300px; margin: 0 10px;" @keydown.enter.native="handleGlobalSearch" /> <FilterPannel style="margin: 0 10px" :filter-pannel-obj="filterPannelObj" @set-filter="handleFilterVals" @reset-filter="getTableData({})" /> <Import :url="`/api/${resourceName}/upload`" @import-success="handleImportSucces" @import-error="handleImportError" /> <Export :url="`/api/${resourceName}/export`" :selected-ids="selected.map(el => el.id)" /> <el-button type="info" icon="el-icon-delete" @click="$refs['table'].clearSelection()" >Clear Selection</el-button> <el-button type="danger" icon="el-icon-delete" @click="handleMultipleDelete">Delete Selected</el-button> </el-col> </el-row> <el-row> <el-table ref="table" v-loading="loading.tableData" :data="tableData" border :row-key="getRowKeys" @sort-change="handleSortChange" @selection-change="handleSelectionChange" > <el-table-column type="selection" label="Selection" reserve-selection /> <el-table-column label="Actions" width="200"> <template slot-scope="scope"> <div style="display: flex; justify-content: space-around;"> <el-button icon="el-icon-view" type="primary" circle @click="$router.push(`/${resourceName}/view/${scope.row.id}`)" /> <el-button icon="el-icon-edit" type="success" circle @click="$router.push(`/${resourceName}/edit/${scope.row.id}`)" /> <el-button icon="el-icon-delete" type="danger" circle @click="handleDeleteClick(scope.row.id)" /> </div> </template> </el-table-column> <el-table-column v-for="field in fieldsToShow" :key="field.name" :prop="field.name" :label="field.name.replace('_',' ')" sortable="custom" > <template slot-scope="scope"> <div v-if="field.type=='multilangtext'" class="cell" >{{ JSON.parse(scope.row[field.name])[$store.state.app.language] }}</div> <div v-if="field.type=='text'" class="cell">{{ scope.row[field.name] }}</div> <el-tag v-if="field.type=='tag'">{{ scope.row.type }}</el-tag> <img v-if="field.type=='image'" :src="scope.row.icon" width="100px" height="100px" /> <div v-if="field.type=='oneFrom'" class="cell"> <el-tag :key="scope.row[field.name]" >{{field.multilang ? scope.row[field.name][$store.state.app.language] : scope.row[field.name]}}</el-tag> </div> <div v-if="field.type=='manyFrom'" class="cell"> <el-tag style="margin: 5px" v-for="(item, index) in scope.row[field.name]" :key="`${field.multilang ? item[$store.state.app.language] : item}+${index}`" >{{field.multilang ? item[$store.state.app.language] : item}}</el-tag> </div> <div v-if="field.type=='boolean'" class="cell"> {{scope.row.id}} => {{field.name}} =>> {{scope.row[field.name]}} =>>> {{fieldData[field.name][scope.row.id]}} <el-switch :key="`switch${scope.row.id}`" v-model="fieldData[field.name][scope.row.id]" :active-value="1" :inactive-value="0" ></el-switch> </div> <div v-if="field.type=='select'" class="cell"> <el-select :key="`select${scope.row.id}`" v-model="fieldData[field.name][scope.row.id]" placeholder="Select"> <el-option v-for="item in field.options" :key="item" :label="item" :value="item" ></el-option> </el-select> </div> </template> </el-table-column> </el-table> </el-row> <el-row> <pagination style="padding: 0;" :total="paginationData.total" :page.sync="paginationData.current_page" :limit.sync="paginationData.per_page" @pagination="handlePagination" /> </el-row> </div> </template> <script> import Pagination from '@/components/Pagination'; import FilterPannel from '@/components/FilterPannel'; import Export from './components/Export'; // ? not needed import Import from './components/Import'; import axios from 'axios'; import Resource from '@/api/resource'; const resourceName = 'coupons'; const ResourceApi = new Resource(resourceName); export default { name: 'CategoryList', components: { Pagination, FilterPannel, Export, Import, }, data() { return { resourceName: resourceName, language: 'en', tableData: [], fieldData: {}, fieldsToShow: [ { name: 'title', type: 'multilangtext' }, // { name: 'description', type: 'multilangtext' }, { name: 'code', type: 'text' }, // { name: 'expiry_date', type: 'text' }, { name: 'type', type: 'tag' }, { name: 'store_id', type: 'oneFrom', url: '/api/stores/', foreignKey: 'store_id', attrName: 'name', multilang: true, prerender: true, }, { name: 'tags', type: 'manyFrom', url: '/api/tags?idsarr=', foreignKey: 'tags', attrName: 'name', multilang: true, prerender: true, }, { name: 'brands', type: 'manyFrom', url: '/api/brands?idsarr=', foreignKey: 'brands', attrName: 'name', multilang: true, prerender: true, }, // { name: 'brands', type: 'text' }, { name: 'is_featured', type: 'boolean', prerender: true }, { name: 'status', type: 'select', options: ['publish', 'draft', 'trash'], prerender: true, }, ], paginationData: { current_page: 0, last_page: 0, per_page: 0, total: 0, }, navigation: { page: 1, limit: 10, sort: '', 'sort-order': 'asc', filters: '', search: '', }, filters: '', selected: [], // ? for selection loading: { tableData: false, }, allData: [], filterPannelObj: { title: { default: '', type: 'Input', label: 'Title', }, description: { default: '', type: 'Input', label: 'Description', }, promo_text: { default: '', type: 'Input', label: 'Promo Text', }, type: { default: [], type: 'checkbox', label: 'Type', src: [ { value: 'coupon', label: 'Coupon' }, { value: 'offer', label: 'Offer' }, ], }, code: { default: '', type: 'Input', label: 'Code', }, store_id: { default: [], type: 'select', label: 'Store', src: [], multiple: true, }, brands: { default: [], type: 'select', label: 'Brands', src: [], multiple: true, }, tags: { default: [], type: 'select', label: 'Tags', src: [], multiple: true, }, cats: { default: [], type: 'select', label: 'Categories', src: [], multiple: true, }, }, }; }, watch: { async 'navigation.search'(newVal, oldVal) { await this.handleGlobalSearch(); }, }, async created() { await this.getTableData({}); this.allData = await ResourceApi.list({ limit: -1 }); // to fill filter dialog selects // To get brands this.filterPannelObj.brands.src = (await axios.get( `/api/brands?limit=-1` )).data.map(({ name, id }) => ({ label: JSON.parse(name)[this.$store.state.app.language], value: id, })); // To get tags this.filterPannelObj.tags.src = (await axios.get( `/api/tags?limit=-1` )).data.map(({ name, id }) => ({ label: JSON.parse(name)[this.$store.state.app.language], value: id, })); // To get categories this.filterPannelObj.cats.src = (await axios.get( `/api/categories?limit=-1` )).data.map(({ name, id }) => ({ label: JSON.parse(name)[this.$store.state.app.language], value: id, })); // To get stores this.filterPannelObj.store_id.src = (await axios.get( `/api/stores?limit=-1` )).data.map(({ name, id }) => ({ label: JSON.parse(name)[this.$store.state.app.language], value: id, })); }, methods: { printScope(x) { console.log('TCL: printScope -> x', x); }, async getTableData(query) { this.loading.tableData = true; const responseData = await ResourceApi.list(query); this.tableData = responseData.data; this.paginationData = this.pick( ['current_page', 'last_page', 'per_page', 'total'], responseData ); Object.keys(this.paginationData).forEach( key => (this.paginationData[key] = parseInt(this.paginationData[key])) ); await this.handlePrerender(); this.loading.tableData = false; }, async handlePrerender() { this.fieldsToShow.forEach(async field => { if (field.prerender) { switch (field.type) { case 'oneFrom': { await this.setRelatedFieldName(field); break; } case 'manyFrom': { await this.setRelatedFieldName(field); break; } case 'boolean': { this.fieldData[field.name] = {}; this.tableData.forEach( data => (this.fieldData[field.name][data.id] = data[field.name]) ); break; } case 'select': { this.fieldData[field.name] = {}; this.tableData.forEach( data => (this.fieldData[field.name][data.id] = data[field.name]) ); break; } } } }); }, // utils pick(propsArr, srcObj) { return Object.keys(srcObj).reduce((obj, k) => { if (propsArr.includes(k)) { obj[k] = srcObj[k]; } return obj; }, {}); }, // ? remember to refactor the parameter id async setRelatedFieldName({ name, type, url, foreignKey, attrName, multilang, }) { this.tableData.forEach(async data => { if (type === 'oneFrom') { data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data[ attrName ]; if (multilang) { data[name] = JSON.parse(data[name]); } } else if (type === 'manyFrom') { data[name] = (await axios.get(`${url}${data[foreignKey]}`)).data.map( idata => (multilang ? JSON.parse(idata[attrName]) : idata[attrName]) ); } }); }, // Sort async handleSortChange(change) { this.navigation.sort = change.prop; if (change.order === 'ascending') { this.navigation['sort-order'] = 'asc'; } else if (change.order === 'descending') { this.navigation['sort-order'] = 'desc'; } await this.getTableData(this.navigation); }, // Pagination async handlePagination(obj) { // obj page obj containing {page: ..., limit: ...} this.navigation.page = obj.page; this.navigation.limit = obj.limit; await this.getTableData(this.navigation); }, // Global Search async handleGlobalSearch() { await this.getTableData(this.navigation); }, // ? Skipped for now // Filters async handleFilterVals(filterparams) { console.log('TCL: handleFilterVals -> filterparams', filterparams); this.navigation.filters = JSON.stringify(filterparams.filters); this.navigation.sort = filterparams.sort.field; this.navigation.sort = 'name'; this.navigation['sort-order'] = filterparams.sort.asc ? 'asc' : 'desc'; await this.getTableData(this.navigation); }, async handleDeleteClick(id) { ResourceApi.destroy(id) .then(res => { this.$message.success('Delete Successfully'); this.getTableData({ page: this.paginationData.current_page }); }) .error(err => { this.$message.error(err); }); }, // Selection methods handleSelectionChange(selection) { this.selected = selection; }, getRowKeys(row) { return row.id; }, // Multiple Delete handleMultipleDelete() { axios .delete( `/api/${this.resourceName}/delete-multiple?ids=${this.selected .map(item => item.id) .join(',')}` ) .then(async () => { this.$message.success('Records deleted successfully'); await this.getTableData({ page: this.paginationData.current_page }); if (this.tableData.length === 0) { await this.getTableData({ page: this.paginationData.current_page, }); } this.$refs.table.clearSelection(); }) .catch(); }, // Import Events handleImportSucces() { this.$message.success('New Data Imported'); this.getTableData({}); }, handleImportError(err) { this.$message.error('There were some errors. CHK console'); console.log(err); }, }, }; </script> <style lang="scss" scoped> </style>
Advertisement
Answer
I guess this is a reactive issue. The value of dict
and actually not affecting the feildData
dict.
changes –
this.fieldData[field.name] = {};
replace this with
self.$set(self.fieldData,field.name,{});
and
this.fieldData[field.name][data.id] = data[field.name] // to //Replace self.$set(self.fieldData[field.name],data.id, data[field.name]);
I think this will fix the issue.Instead of using = use $set for value assignment.