【问题标题】:vue managing form editing state, boilerplate codevue 管理表单编辑状态,样板代码
【发布时间】:2018-11-27 17:22:23
【问题描述】:

我的应用中有大量表单,用户可以在其中选择编辑、还原或保存对对象的更改,这些更改最终会保存到后端。

与此非常相似:(代码在另一个问题中找到)
https://jsfiddle.net/k5j6zj9t/22/

var app = new Vue({
      el: '#app',
      data: {
        isEditing: false,
        user: {
          firstName: 'John',
          lastName: 'Smith',
        }
      },
      mounted() {
        this.cachedUser = Object.assign({}, this.user);
      },
      methods: {
        save() {
          this.cachedUser = Object.assign({}, this.user);
          this.isEditing = false;
        },
        cancel() {
          this.user = Object.assign({}, this.cachedUser);
          this.isEditing = false;
        }
      }
    })

由于v-model 绑定会立即更改底层对象,因此我必须首先创建对象的克隆。此外,无论对象是否处于编辑状态,我都需要保存一个数据成员。
将此代码乘以更多表单和字段,我最终会得到太多数据成员和大量样板代码。

在 django 等服务器框架中,模型在保存之前处于“临时状态”,所以我可以这样编辑

user.first_name = 'aa' # temporary object in memory
user.save() # saved to the db

我的问题,是否有一个模型组件/模式可以让 vue 更好地处理这个任务?
将保持模型状态的东西 - 即isEditing,自动克隆对象以进行表单编辑,恢复更改等。
这样我就不用为这么多的对象写这样的代码了?

【问题讨论】:

  • 我会为此使用mixin。因此,您只需编写 1 个 mixin 并在任何有此类表单的地方使用它。
  • @Brissy 你能详细说明一下吗?我正在寻找几个小时,但没有找到解决方案
  • this 能解决您的问题吗?
  • 不完全是,我有一个绑定多个表单的 vue 实例
  • 为什么我们不能将每个表单移动到它自己的组件并使用 mixin 扩展它?恐怕因为你所有的形式都不一样,你不能统一它:(

标签: forms vue.js crud


【解决方案1】:

使用Scoped Slots可以满足您的要求。

我的解决方案

  1. 用一个槽创建一个组件

  2. 那么这个槽会绑定valuesclonedValues(如果closeMode为假,clondedValues = values

  3. 最后,在父组件中,使用作用域插槽的属性生成模板,然后将其传递给插槽。

如下演示:

Vue.component('child', {
  template: `
  <div>
    <div>
      <slot v-bind:values="clonedValues"></slot>
    </div>
    <p>
      <button @click="saveAction(clonedValues)">Save</button>
      <button @click="resetAction()">Reset</button>
    </p>
  </div>`,
  props: {
    'cloneMode': {
      type: Boolean,
      default: true
    },
    'values': {
      type: Object,
      default: () => { return new Object() }
    }, 
    'saveAction': {
      type: Function,
      default: function (newValues) {
        this.$emit('save', newValues)
      }
    }, 
    'resetAction': {
      type: Function,
      default: function () {
        this.syncValues(this.values)
      }
    }
  },
  data() {
    return {
      clonedValues: {}
    }
  },
  created: function () {
    this.syncValues(this.values)
  },
  watch: {
    values: {
      handler: function (newVal) {
        this.syncValues(newVal)
      },
      deep: true
    },
    cloneMode: function () {
      this.syncValues(this.values)
    }
  },
  methods: {
    syncValues: function (newVal) {
      this.clonedValues = this.cloneMode ? Object.assign({}, newVal) : newVal // if you'd like to support nested object, you have to deep clone
    }
  }
})

Vue.config.productionTip = false

app = new Vue({
  el: "#app",
  data: {
    mode: true,
    labels: ['id', 'name'],
    childForm: {
      'id': 1,
      'name': 'test'
    }
  },
  methods: {
    saveForm: function (ev) {
      Object.keys(this.childForm).forEach((item) => {
        this.childForm[item] = ev[item]
      })
      // call backend to update the data
    },
    changeCurrentValue: function () {
      this.childForm.id += '#'
      this.childForm.name += '@'
    }
  }
})
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id="app">
  <p><button @click="mode=!mode">Mode: {{mode}}</button></p>
  <p>Current: {{childForm}}  --<button @click="changeCurrentValue()">Change Current</button></p>
  <child :values="childForm" @save="saveForm($event)" :clone-mode="mode">
    <template slot-scope="slotProps">
      <p>ID: <input v-model="slotProps.values['id']"/></p>
      <p>Name: <input v-model="slotProps.values['name']"/></p>
    </template>
  </child>
</div>

针对请求的 OP 进行编辑:

  1. 将默认槽更改为命名槽=edit,然后创建一个槽=视图

  2. 添加数据属性=编辑,如果为真,则显示“编辑”槽,如果为假,则显示“查看”槽。

  3. 在父组件中,为 slot=view 设计模板。

如下演示:

Vue.component('child', {
  template: `
  <div>
    <div v-show="editing">
      <slot name="edit" v-bind:values="clonedValues"></slot>
      <button @click="saveForm(clonedValues)">Save</button>
      <button @click="resetAction()">Reset</button>
    </div>
    <div v-show="!editing">
      <slot name="view"></slot>
      <button @click="editing = true">Edit</button>
    </div>
  </div>`,
  props: {
    'values': {
      type: Object,
      default: () => { return new Object() }
    }, 
    'saveAction': {
      type: Function,
      default: function (newValues) {
        this.$emit('save', newValues)
      }
    }, 
    'resetAction': {
      type: Function,
      default: function () {
        this.syncValues(this.values)
      }
    }
  },
  data() {
    return {
      editing: false,
      clonedValues: {}
    }
  },
  created: function () {
    this.syncValues(this.values)
  },
  watch: {
    editing: function (newVal) {
      if(newVal) this.syncValues(this.values)
    },
    values: {
      handler: function (newVal) {
        if(this.editing) this.syncValues(newVal) //comment out this if don't want to sync latest props=values
      },
      deep:true
    }
  },
  methods: {
    syncValues: function (newVal) {
      this.clonedValues = Object.assign({}, newVal) // if you'd like to support nested object, you have to deep clone
    },
    saveForm: function (values) {
      this.saveAction(values)
      this.editing = false
    }
  }
})

Vue.config.productionTip = false

app = new Vue({
  el: "#app",
  data: {
    childForm: {
      'id': 1,
      'name': 'test'
    }
  },
  methods: {
    saveForm: function (ev) {
      Object.keys(this.childForm).forEach((item) => {
        this.childForm[item] = ev[item]
      })
      // call backend to update the data
    },
    changeCurrentValue: function () {
      this.childForm.id += '#'
      this.childForm.name += '@'
    }
  }
})
<script src="https://unpkg.com/vue@2.5.16/dist/vue.js"></script>
<div id="app">
  <p>Current: {{childForm}}  --<button @click="changeCurrentValue()">Change Current</button></p>
  <child :values="childForm" @save="saveForm($event)">
    <template slot-scope="slotProps" slot="edit">
      <h3>---Edit---</h3>
      <p>ID: <input v-model="slotProps.values['id']"/></p>
      <p>Name: <input v-model="slotProps.values['name']"/></p>
    </template>
    <template slot="view">
      <h3>---View---</h3>
      <p>ID: <span>{{childForm['id']}}</span></p>
      <p>Name: <span>{{childForm['name']}}</span></p>
    </template>
  </child>
</div>

【讨论】:

  • 谢谢。是否可以在根组件元素(&lt;form&gt;)上有插槽范围?而不是在包装器 div 上。此外,插槽范围是组件导出方法的唯一方法吗?而且,我在这里没有看到编辑选项,我想我只会在编辑时克隆对象,而不是在初始化时,是否可以在这个组件中实现它?另外我不需要cloneMode,编辑时总是需要clone
  • @user3599803 更新了答案(添加数据属性=编辑)。对于您的第一个问题,您的意思是将子组件的根元素从 div 更改为 form?
  • 我要把根元素改成form,我的意思是是否可以跳过
猜你喜欢
  • 2021-08-18
  • 2023-01-18
  • 1970-01-01
  • 2011-03-01
  • 2019-03-25
  • 1970-01-01
  • 2017-01-22
  • 2021-01-24
  • 2020-03-12
相关资源
最近更新 更多