Rendering Configurable Forms with Vue--A Questionnaire Platform Project

In recent days, there has been an urgent project to make an internal version of the questionnaire star. It is equivalent to a platform for editing questionnaires and providing questionnaires for displaying data statistics. The whole project is not long-term, in line with the principle of accumulation and accumulation, the process of thinking and harvest for a precipitation. For company reasons, the code is not yet open source.

However, an attempt to dynamically configure the form was precipitated: github Used for rapid development of forms and other requirements in the background, with element-ui for use, at the same time, form can be generated through configuration in the background.

Functions and effects

Questionnaire editing function may need the following points:

  • Adding questions according to different types of questions

  • The Necessity of Distinguishing Problems

  • Question sorting, deletion, replication function

  • Option Editing, Sorting and Deleting of Selection Questions

  • Questionnaire rendering

  • Generation of Questionnaire Two-Dimensional Code

Effect

Technical scheme

Vue + VueRouter + ElementUI

It's perfectly appropriate to use element to render background and questionnaire forms. It saves a lot of time to modify the form style, and makes it possible and easy to render the form dynamically.

Form dynamic rendering

Just before the project, there was an attempt to dynamically configure forms: github Forms are automatically generated and validated by fields. However, the data format at this time is equivalent to that already determined in the background. For the form structure which can change frequently, the data structure is determined as follows:

data structure

data: {
  title: Questionnaire Name
  desc: Questionnaire description
  questionList: [
    {
      type: Type of problem,
      label: Problem Description,
      required: Necessity of selection,
      options: [ //option
        {
          label: Option content,
          value: Option value
        }
        ...
      ] 
    }
    ...
  ]
}

Form Rendering

The simplest v-if mode to meet our needs, I had thought about using is to render, but different form configuration items are very different, it is difficult to use. Therefore, similar to the following, the configuration details can be found on the element ary website.

<!-- Completion -->
<div v-if="question.type === 'input' || question.type === 'textarea'" class="question-content-wrap">
  <el-row>
    <el-col :xs="8" :sm="10">
      <el-input
        v-model="question.value"
        :autosize="{ minRows: 2, maxRows: 4}"
        class="question-input"
        :type="question.type">
      </el-input>
    </el-col>
  </el-row>
</div>

It's easy to render the form according to configuration:

Implementation process

If you have a clear idea, you can put it into practice.

Adding problems

First, I need basic configuration templates for each problem so that I can add the corresponding content directly to the questionList each time. To facilitate storage and use, I put it in the store.

const state = {
  baseSet: {
    radio: {
      type: 'radio',
      label: 'Single-choice topic',
      required: true,
      options: [...]
    },
    checkbox: ...
    input: ...
  }
}

//When adding a problem, push directly into the array.
const mutations = [
  //Adding problems
  ADDQUESTIONLIST(state, data) {
    state.qss.questionList.push(data);
  }
]

//Adding Problem Method
addQuestion(type) {
  this.addQuestionList(this. baseSet[type]);
},

Be careful

When we use getter to get our corresponding baseSet object, it is a reference type, and the attributes of the object, such as options, are also a reference type. If we don't deal with it, we will see that when we create two problems of the same type, we will modify one of them and the other. So we need to make a simple copy of the base object (just to the contents of the array)

export const clone = function(obj) {
  var newObj = {};

  for (let key in obj) {
    var target = obj[key];

    if (Object.prototype.toString.call(target) === "[object Object]") {
      newObj[key] = clone(target);
    } else {
      if (Object.prototype.toString.call(target) === "[object Array]") {
        newObj[key] = target.slice(0);
      } else {
        newObj[key] = target;
      }
    }
  }

  return newObj;
}

addQuestion(type) {
  this.addQuestionList(clone(this. baseSet[type]));
},

Sort/delete/copy

These three points are basically simple array operation, at this time, the problem data is still reference type, directly on the reference array can be operated. Simple up and down sorting can be achieved by splice. In fact, these three points are implemented by splice.

deleteQuestion(index) {
  this.data.questionList.splice(index, 1);
},

copyQuestion(index) {
  let list = this.data.questionList;
  //When copying, you also need to make a deep copy of the reference object
  list.splice(index, 1, list[index], clone(list[index]));
},

moveQuestion(index, direct) {
  let list = this.data.questionList;

  if(direct === 'up') {
    if(index < 1) {
      this.$toast('It's the first!');
      return;
    }

    list.splice(index - 1, 2, list[index], list[index - 1]);
  } else {
    if(index >= list.length - 1) {
      this.$toast('This is the last item!');
      return;
    }

    list.splice(index, 2, list[index + 1], list[index]);
  }
}

Generating two-dimensional codes

Use qrcode.js Thank the big boys for creating so many useful wheels for the younger generation, let us stand on the shoulders of giants!

Other points

For Vuex, how to use computed to get getters or state with v-model?

As we all know, after Vue2.0, we use computer to get getters or state, but for computing attributes, we can't write, like this.

computed: {
  ...mapState({
    qss: state => state.qss,
    base: state => state.base
  })
},

//The following code is invalid
this.qss = 2;

Therefore, we can not directly bind the qss attribute to v-model, which is very distressing. A common way for colleagues to do this is to write the same attributes in data, initialize them on route entry, and write back to store when they modify them. It's a bit cumbersome and inappropriate to write like this. So, how to solve it?

In fact, it's very simple. It can be handed over to the parent component.

We often hear a word, one-way data flow, which means that the data flow in a single direction. We only modify the data source, and then let the data flow from the data source to the sub-components for UI rendering.

In fact, just like when we use ajax to get data, we will give the data to the parent component. We will use props to distribute the data downwards, and use vuex to do the same. Subcomponent values are modified for corresponding values. For props, v-model can be easily modified. Of course, these are just my understanding. If there is any objection, we can discuss them together.

Keywords: Javascript github Vue QRCode Attribute

Added by hellonoko on Tue, 09 Jul 2019 02:29:59 +0300