Code address: gitee.com/wyh-19/supe… This article is based on the following code: Essay-8

Series of articles:

  • Vue + Element Large Forms solution (1)– Overview
  • Vue + Element Large Forms solution (2)– Form splitting
  • Vue + Element Large Forms Solution (3)– Anchor Components (part 1)
  • Vue + Element Large Form Solution (4)– Anchor Component (2)
  • Vue + Element Large Forms solution (5)– Validation identifier
  • Vue + Element Large Forms solution (6)– Automatic identification
  • Vue + Element Large Forms solution (7)– Form form
  • Vue + Element Large Form Solution (8)– Data comparison (1)
  • Vue + Element Large Form Solution (9)– Data comparison (2)
  • Vue + Element Large Forms solution (10)– Form communication and dynamic forms

preface

Form data comparison is not a common requirement, but the form operator wants to see the current form data and the data before the change, when the data is different, give a hint and can see the previous data, very similar to the meaning of code comparison. While this article is actually about comparing data to simple forms, the comparison of irregular forms does borrow some of the interaction and logic of code comparison, which will be covered after the introduction of irregular forms.

The preparatory work

At the end of the previous chapter, data acquisition and echo were demonstrated. When the form form is compare, two sets of data will be obtained and paid respectively to formDataMap and oldFormDataMap in data, where oldFormDataMap is the old form data. Now add attribute passing to each subform, pass oldFormDataMap to each subform, just like formDataMap, and change the code to use the subform component:

.<form1 ref="form1" form-key="form1" :data="formDataMap" :old-data="oldFormDataMap"
           @validate="handleValidate" />.<form2 ref="form2" form-key="form2" :data="formDataMap" :old-data="oldFormDataMap"
           @validate="handleValidate" />.Copy the code

Modify the mixin file shared by subform components to increase data processing for oldData, and increase data processing for props, data, computed, and Watch in sequence. The codes are as follows (original data related codes are attached):

// props:
data: {
  type: Object.default: () = >({})},oldData: {
  type: Object.default: () = >({})}// data:
formData: {},
oldFormData: null.// computed:
partFormData() {
  return this.data[this.formKey]
},
partOldFormData() {
  return this.oldData[this.formKey]
},

// watch:
partFormData: {
  handler(newValue) {
    this.formData = easyClone(newValue) || {}
  },
  immediate: true
},
partOldFormData(newValue) {
  // When there is no old data, hope is empty
  this.oldFormData = easyClone(newValue)
}
Copy the code

Note that when oldFormData is initialized as null and oldFormData is localized, it is not forced to convert an empty deep-copy result to an empty object. Instead, the result is expected to be NULL so that no data comparison can be performed when the result is null.

Implementation approach

Obviously, the easiest thing to think of is to add a component to each field item and use v-if to determine that the component is displayed when there is old data and the item’s old data is different from the new data. But since we are working on a large form, the total number of form items will be very large, and it would be particularly unfriendly to have a V-if followed by each item. Fortunately, Vue provides the ability to customize instructions, which makes it possible to add DOM structures in a non-invasive way, so it’s especially suited for this scenario. After designing custom instructions, you only need to increase the use of instructions on each form item, and whether to display the data difference mark is completely handled by the instructions, which is far more efficient than adding template code at the end.

The specific implementation

The new directive

Add the directives directory in the SRC directory and add the index.js and v-compare folders. In the V-compare directory, add the JS file and fill in the standard code for the Vue directive plug-in as follows:

export default {
  install(Vue) {
      Vue.directive('compare', {
          // The hook function}}})Copy the code

The index.js in the cache directory imports V-compare and registers the component with vue. use(VCompare), and finally imports the caching folder at the system entrance to reference the component in the system. The details will not be described.

Determine the form of use

Now the code for a form item looks like this:

<el-input v-model="formData.name" />
Copy the code

Naturally, the use command should look something like this:

<el-input v-model="formData.name" v-compare="oldFormData.name"/>
Copy the code

Oldformdata. name and v-model formData.name are compared internally. But when I designed oldFormData, IT was allowed to be null. One more thing to say here, because I’m thinking that if I didn’t design it this way, I would call undefined when I call a property that doesn’t exist, and then I wouldn’t be able to tell whether that property is because it comes back with no value or because there’s no oldFormData at all. So the above should at least be changed to the following form to avoid error:

<el-input v-model="formData.name" v-compare="oldFormData&&oldFormData.name"/>
Copy the code

This was very wordy and not what I wanted, so I finally adopted the following form:

<el-input v-model="formData.name" v-compare:name="oldFormData"/>
Copy the code

Select the lifecycle hook

According to the official VUE documentation, the VUE directive has the following five lifecycle hook functions

  • Bind: Called only once, the first time a directive is bound to an element.
  • Inserted: Called when the bound element is inserted into the parent node.
  • Update: called when the component’s VNode is updated, but may occur before its child VNodes are updated. The value of the instruction may or may not have changed.
  • ComponentUpdated: Invoked when the VNode of the component where the directive resides and its child VNodes are all updated.
  • Unbind: Called only once, when an instruction is unbound from an element.

While bind and INSERTED, it is clear that the form has not received data yet, so no data comparison can be performed. Update and componentUpdated, here I choose the latter, mainly because the timing of comparison is not so urgent.

Data extraction

The instruction hook function can take the following arguments

  • El: The element bound by the directive that can be used to manipulate the DOM directly.
  • Binding: An object containing the following properties:
    • Name: indicates the command name, excluding the V – prefix.
    • Value: specifies the binding value of the directive. For example, v-my-directive=”1 + 1″, the binding value is 2.
    • OldValue: The previous value of the directive binding, available only in the UPDATE and componentUpdated hooks. Available regardless of whether the value changes.
    • Expression: command expression in the form of a string. For example, if v-my-directive=”1 + 1″, the expression is “1 + 1”.
    • Arg: Optional parameter passed to the instruction. For example, v-my-directive:foo, the parameter is “foo”.
    • Modifiers: An object that contains modifiers. For example, in v-my-directive.foo.bar, the modifier object is {foo: true, bar: true}.
  • Vnode: virtual node generated by Vue compilation.
  • OldVnode: Last virtual node, available only in update and componentUpdated hooks.

The above is from the official document, and the following parameters are used:

  1. El: that is, mount the DOM structure of the form item component of the instruction, dom process it, add additional DOM structures and bind events to it.
  2. Value and oldValue: the value of the expression to the right of the V-compare directive before and after component updates, which is passed inoldFormDataWhen oldFormData changes from no data to data, the comparison is performed once, i.e! oldValue && value.
  3. Arg: expressionv-compare:nameIs used to indicate the name of the field
  4. Expression modifiers, not used here but used later.
  5. Vnode: A vUe-compiled virtual node from which data in the V-Model can be accessed for comparison.

coded

Now that we’ve done a lot of basic explaining and preparation work, let’s start with the implementation and add hook functions:

componentUpdated(el, binding, vnode) {
    const { value, oldValue, arg } = binding
    // oldFormData is compared only when there is data from no data
    // Avoid too many invalid alignments
    if(! oldValue && value) {// The comparison function is available when entering this if judgment
      // The latest data, that is, the current binding value in the V-model
      const lastModel = vnode.data.model.value
      // oldFormData[arg]
      const beforeModel = value[arg]
      // If the two data are different
      if(lastModel ! = beforeModel) {// Label it
        markDiffrent(el, beforeModel)
      }
    }
}
Copy the code

The specific marking method code is as follows:

// When creating a DOM structure, use CSS directly. CSS code is omitted
import './index.css'

function markDiffrent(el, text) {
  if (text === undefined || text === null || text === ' ') {
    text = 'No data'
  }
  text = text.toString()
  // Create a flag
  const pop = document.createElement('div')
  pop.className = 'v-compare-pop'
  // Create a small prompt bubble on the logo
  const tip = document.createElement('div')
  tip.className = 'v-compare-tip'
  tip.style.display = 'none'
  const tipContent = document.createElement('div')
  tipContent.className = 'v-compare-tip-content'
  tipContent.textContent = text
  tip.appendChild(tipContent)
  // Add click events to the flag and toggle tip implicit
  pop.addEventListener('click', handleClick, true)
  pop.appendChild(tip)
  el.appendChild(pop)
  // Cache the POP element in el for easy destruction access
  el.__pop__ = pop
}
// Click control to hide the display
function handleClick(e) {
  e.stopPropagation()
  if (e.target.className === 'v-compare-pop') {
    const tip = e.target.childNodes[0]
    if (tip.style.display === 'none') {
      tip.style.display = 'block'
    } else {
      tip.style.display = 'none'}}}Copy the code

The markDiffrent method is relatively simple. It creates a marker DOM element on the DOM element of the component where the instruction is located and adds a tip function to it. Since the compare directive binds click events, these events are destroyed when the directive is destroyed:

unbind(el) {
    // Find the referenced DOM element from el
    const pop = el.__pop__
    if (pop) {
      pop.removeEventListener('click', handleClick)
    }
}
Copy the code

Add a comparison command to all form items:

form1.vue
...
<el-input v-model="formData.age" v-compare:age="oldFormData" />. form2.vue ...<el-input v-model="formData.company" v-compare:company="oldFormData" />.Copy the code

The effect is as follows:

So far, the data comparison instruction has been preliminatively completed, but only the simplest text form items have been processed, so how do select, radio and other form items echo the comparison data, here due to the space and time factors, I will continue in the next chapter.Thank you for reading, and your comments are welcome!