Skip to content
Advertisement

v-model is not changing data with switch and select dropdown in vuejs

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.

Codepen – https://codepen.io/Pratik__007/pen/gObGOKx

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement