A flash of light! Help you use Vue to solve the unsolvable "dynamic mount"

Keywords: prototype autocomplete

In some special scenarios, the timing of using components cannot be determined, or the components we want to use cannot be determined in Vue's template. In this case, we need to mount components dynamically, or use runtime compilation to dynamically create and mount components.

Today, we will take you from the actual project to see how to dynamically mount components when actually solving customer problems, and show you a complete process to solve the problem of dynamic mounting.

Unresolved "dynamic mount"

When our spreadsheet control SpreadJS is running, there is such a function: when the user double clicks a cell, an input box will be displayed for editing the content of the cell. The user can customize the Form of the input box according to the specification of user-defined cell type and integrate it into any Form input type.

The creation and destruction of this input box are realized by inheriting the corresponding method of cell type, so there is a problem here - this dynamic creation method can not be simply configured in VUE template and used directly.

Not long ago, the customer asked me: does the custom cell of your control support Vue components such as ElementUI AutoComplete

Due to the problem mentioned earlier:

After pondering for a long time, I seriously replied to the customer: "the operation life cycle of components is inconsistent and can not be used", but the conversation changed again, indicating that general components can be used to solve this problem.

The problem was solved smoothly.

But this helpless "useless" has also become a barrier that I can't cross in my midnight dream these days.

Later, when I read the Vue document one day, I thought that the app was attached to #app at runtime., Theoretically, other components should also be able to dynamically mount to the required Dom, so the problem of creating timing can be solved!

Officially open dynamic mount

Let's continue with the documentation, global API Vue.extend( options ) It is created through extend. Vue instances can use $mount Methods are mounted directly on DOM elements -- that's what we need.

<div id="mount-point"></div>
 
// Create constructor
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// Create a Profile instance and mount it on an element.
new Profile().$mount('#mount-point')

according to SpreadJS Custom cell example: create AutoCompleteCellType and set it in the cell:

function AutoComplateCellType() {
}
AutoComplateCellType.prototype = new GC.Spread.Sheets.CellTypes.Base();
AutoComplateCellType.prototype.createEditorElement = function (context, cellWrapperElement) {
//   cellWrapperElement.setAttribute("gcUIElement", "gcEditingInput");
  cellWrapperElement.style.overflow = 'visible'
  let editorContext = document.createElement("div")
  editorContext.setAttribute("gcUIElement", "gcEditingInput");
  let editor = document.createElement("div");
  // The editorContext in the user-defined cell is used as a container. You need to create a child for mounting. It cannot be directly mounted to the editorContext
  editorContext.appendChild(editor);
  return editorContext;
}
AutoComplateCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
    let width = cellRect.width > 180 ? cellRect.width : 180;
    if (editorContext) {
      // Create constructor
      var Profile = Vue.extend({
        template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
        data: function () {
          return {
            firstName: 'Walter',
            lastName: 'White',
            alias: 'Heisenberg'
          }
        }
      })
      // Create a Profile instance and mount it on an element.
      new Profile().$mount(editorContext.firstChild);
    }
};

Run, double-click to enter the editing status, but it is found that an error is reported

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

According to the error prompt, we have two solutions at this time:

  1. Start runtimeCompiler and add the configuration of runtimeCompiler: true in vue.config.js to allow runtime compilation. In this way, the template can be generated dynamically to meet the needs of dynamic components
  2. The template compiled in advance is only dynamically mounted. The components of autocomplete are determined. We can use this method

Create a new AutoComplete.vue component for dynamic mounting, so that the compiled components can be mounted.

<template>
  <div>
    <p>{{ firstName }} {{ lastName }} aka {{ alias }}</p>
  </div>
</template>
<script>
export default {
  data: function () {
    return {
      firstName: "Walter",
      lastName: "White",
      alias: "Heisenberg",
    };
  },
};
</script>
 
 
import AutoComplate from './AutoComplate.vue'
 
 
AutoComplateCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
    let width = cellRect.width > 180 ? cellRect.width : 180;
    if (editorContext) {
      // Create constructor
      var Profile = Vue.extend(AutoComplate);
      // Create a Profile instance and mount it on an element.
      new Profile().$mount(editorContext.firstChild);
    }
};

Double click to enter the editing status and see the contents in the component

Next, for custom cells, you need to set and obtain the editing content in the component. At this time, add props to the component and directly obtain all props content on the VueComponent instance created during mounting. The corresponding operation can realize data acquisition settings.

Update autocomplete.vue, add props, and add input for editing

<template>
  <div>
    <p>{{ firstName }} {{ lastName }} aka {{ alias }}</p>
    <input type="text" v-model="value">
  </div>
</template>
<script>
export default {
  props:["value"],
  data: function () {
    return {
      firstName: "Walter",
      lastName: "White",
      alias: "Heisenberg",
    };
  },
};
</script>

Store the VueComponent instance through this.vm, and obtain and set the Value to the VUE component in the getEditorValue and setEditorValue methods. After editing, destroy the dynamically created component by calling the $destroy() method.

AutoComplateCellType.prototype.activateEditor = function (editorContext, cellStyle, cellRect, context) {
    let width = cellRect.width > 180 ? cellRect.width : 180;
    if (editorContext) {
      // Create constructor
      var Profile = Vue.extend(MyInput);
      // Create a Profile instance and mount it on an element.
      this.vm = new Profile().$mount(editorContext.firstChild);
    }
};
 
AutoComplateCellType.prototype.getEditorValue = function (editorContext) {
    // Set component defaults
    if (this.vm) {
        return this.vm.value;
    }
};
AutoComplateCellType.prototype.setEditorValue = function (editorContext, value) {
    // Gets the edited value of the component
    if (editorContext) {
      this.vm.value = value;
    }
};
AutoComplateCellType.prototype.deactivateEditor = function (editorContext, context) {
    // Destroy components
    this.vm.$destroy();
    this.vm = undefined;
};

After the whole process runs through, you only need to replace the input with el- autocomplete of ElementUI in autocomplete.vue and implement the corresponding method.

result

Let's see the effect.

In fact, dynamic mount is not a complex operation. I understand Vue For example, it is not difficult to operate instances through vm and flexibly use components that are dynamically mounted or compiled at run time.

In fact, all the solutions are in the Vue tutorial introductory tutorial, but the use of scaffolding and various tools make us forget Vue's original intention, but complicate the simple problems.

This is the end of today's sharing. The follow-up will bring you more serious and interesting content~

Do you have anything to "forget your original heart" in development?

Posted by MattWeet on Thu, 28 Oct 2021 12:28:15 -0700