Product demand

After the meeting on product requirements, I came across a requirement. First, the page was divided into two columns. On the left side, data components were displayed, which supported drag and drop sorting. On the right, you can drag a thumbnail of a component to the left to create a new component.

Train of thought

For dynamically generated components, where a new component is generated each time, you can put the component in a return function. A function is called in JSX, and each time the function is called, a completely new component is returned. This is very simple for React, but for Vue, it is not possible to return components directly. Although this return is not suitable for Vue, we cannot deny that the idea is very correct, so we should consider another way to write it. As for building components dynamically, we must drive the generation of components with data. For the sort of drag and drop components, directly use the drag and drop library OK!!

Problems faced

  1. Drag and drop library selection
  2. How to generate components
  3. Dynamically generate components with data drive

Drag and drop library selection

In this case, I’ve chosen vue. Draggable, a drag-drop library that exists in the project. If you don’t use the drag-and-drop library in your Vue project, you can refer to the design ideas in this article.

How to generate components

Extend (); extend(); extend(); Next we create a JS file to write the code to create the component.

Generate the component

/* generatecomponents.js file name */ import Vue from "Vue "; // To dynamically generate components, import this file first. import components1 from "./components/TestCom1.vue"; import components2 from "./components/TestCom2.vue"; Const comMap = {components1, components2,}; const comMap = {components1, components2,}; Const ReturnNewCom = function ({props, on}) {const {comItem: function ({props, on}) {const {comItem: { name }, } = props; Const newComponent = vue.extend ({render(createElement) {// Use the passed component name to determine which component to render. return createElement(comMap[name], { props, on, }); }}); return new newComponent(); }; export default ReturnNewCom;Copy the code

component

Here we write two components, which is used to demonstrate this Demo, components1 respectively. Vue, components2. Vue.

/*components1.vue*/ <template> <div class="widget-wrapper"> <header class="header">{{ comDetail.name }}--{{ comDetail.id </header> <h1> {{queryObj}}</h1> <button @click="handleDelete"> </button> </div> </template> <script> return { comDetail: this.comItem, _queryObj: this.queryObj, }; }, props: { comItem: { type: Object, default() { return { id: 0, name: "", }; },}, queryObj: {// can accept the parent component to pass the filter condition, must be Object type: Object, default() {// define the default query condition. return { num: 0, }; }, }, }, watch: { comItem(val) { this.comDetail = val; return val; }, queryObj(val) { this._queryObj = val; return val; }, }, created() { console.log("data -> this.comItem", this.comItem); }, methods: {handleDelete() {this.$el.remove(); // Call the parent function. Modifies the data in the leftComList array in the parent component. this.$emit("handleDelete", this.comDetail); ,}}}; </script> <style scoped> .widget-wrapper { background: #ff7b7b; border-radius: 12px; overflow: hidden; width: 200px; } .header { height: 50px; padding: 0 15px; } </style>Copy the code

In fact, the code in componentS2.vue and componentS2.vue file code is similar, the only difference is that the background color is not the same.

Data-driven generation of dynamic components

The next step is to use the ue.Draggable library for dragging and data modification. We can write directly in the app.vue file.

/* App.vue */ <template> <div class="dragCom"> <h1>{{ leftComList }}</h1> <button <div class="body"> <div class="left"> <draggable class="left"> :list="leftComList" :group="'people'"> <div ref="comBody" v-for="({ name, id }, index) in leftComList" :key="id" class="comCard" > <! -- Loop through the leftComList array, using the data to render the component, adding the dynamically generated array to the DOM element. --> {{ handleAddCom({ props: { comItem: { name, id }, queryObj }, index, }) }} </div> </draggable> </div> <div class="right"> <draggable class="dragArea" :list="rightComList" :group="{ name: 'people', pull: 'clone', put: false }" :clone="handleCloneDog" > <div class="card" v-for="element in rightComList" :key="element.id"> {{ element.name }} </div> <! -- Card data on the right, </div> </div> </div> </div> </template> <script> import draggable from "vuedraggable"; import CreateCom from "./generateComponents"; export default { components: { draggable, }, data() { return { rightComList: [ { id: Math.random(), name: "Components1 ",}, {id: components.random (), name: "components2",},], leftComList: [], // Store data for dynamic component generation. ComMap: new Map(), // the main function is to record whether // the component is rendered in the DOM class="comCard", // if it is rendered, no more subelements can be added. QueryObj: {// pass num: 0,},}; }, beforeDestroy() {// clear the recorded data this.map.clear (); }, methods: { handleAddCom({ index, on = {}, props = { comItem: { name: "", id: 0 } } }) { const { comItem: { id }, } = props; This.$nextTick() => {const childNodesLength = this.codes.body [index].childnodes.length; Const comLine = this.body; if (! This.map.get (id)) {this.map.get (id)) {this.map.get (id); Create the component by calling the CreateCom method. Const com = CreateCom({props, on: {handleDelete: this. HandleDeleteCom,... on, }, }); // 2. Generate component com.$mount(); If (childNodesLength === 2) {// If you want to add between two components. Then put the newly generated component DOM location change in the middle. This.codes.body. Splice (index, 0, this.codes.body [comline-1]); } // 3. Add the generated component to the DOM. this.$refs.comBody[index].appendChild(com.$el); // 4. Record that this component implements rendering. this.comMap.set(id, true); } else {// The component at this location has already been rendered, and does not need to render again; }}); }, handleDeleteCom({id}) {// pass to the child component deletion method, According to the component's id to delete data const index = this. LeftComList. FindIndex ((item) = > item. The id = = = id); If (~index) {// delete this.leftComList.splice(index, 1); }}, handleCloneDog(item) {// Add data to leftComList array return {... item, id: Math.random(), }; ,}}}; </script> <style> .dragCom { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } .body { width: 100%; height: 800px; display: flex; justify-content: space-between; } .left { flex: 1; height: 800px; border: 1px solid pink; } .right { width: 20%; height: 800px; } .card { height: 50px; background-color: #40cec7; margin: 12px 0; font-size: 12px; line-height: 50px; cursor: pointer; } .comCard { margin: 12px; display: inline-block; } </style>Copy the code

This enables dynamic component rendering and drag-and-drop sorting.

The effect

The source code

You can download the source code for this article on Github if you want to try it out

Previous excellent article

Guide to Optimizing WebPack Packaging