How to Build a Drag‑Drop Site Builder with Vue.js: From Data Model to Rendering
This article explains how to design and implement a Vue.js‑based website builder that lets users create multi‑page sites by dragging modules, covering requirement analysis, JSON data modeling, dynamic component rendering, state management with Vuex, and drag‑and‑drop integration.
As a frontend engineer, you may have built many websites and applications. This article classifies sites into "presentation" and "operation" types, and describes the design and implementation of an "operational" site that serves as a "site‑builder for websites" using Vue.js.
Requirements
Typical website‑builder features include:
Providing templates
Theme colors
Rich functional modules such as text, carousel, gallery, etc.
Draggable modules with configurable options
Support for creating multiple pages
Page layout with sections/columns
Responsive design for handheld devices
Requirement Analysis
Key concepts extracted from the requirements are "template", "theme color", "module", "page", and "section". Their meanings are clarified to guide the design.
Page : A website consists of one or more pages.
Section : A page is divided into functional areas (e.g., company intro, case studies, contact). We refer to these as "sections".
Module : Each section contains one or more components (modules) that achieve a specific purpose. A module corresponds to a Vue component.
Template : Defines the layout of a page, including the number of sections and their horizontal proportions.
Theme Color : Combined with a template to form the site’s visual style.
Website Data Representation
The site is represented as a tree‑structured JSON object, where a site contains pages, pages contain sections, and sections contain modules. This hierarchical model can be visualized as:
Example JSON representation:
<code>{
"id": 1,
"name": "xxx公司",
"type": "site",
"children": [{
"type": "page",
"name": "首页",
"children": [{
"type": "section",
"name": "公司简介",
"children": [{
"type": "paragraph"
}, {
"type": "carousel"
}]
}]
}]
}
</code>Modules are leaf nodes; each module has
content(the actual data) and
config(settings such as animation or autoplay for a carousel).
Theme color is stored in
site.config.themeColor. Responsive layout can be achieved by adding Bootstrap grid classes to a section’s
config, e.g.,
class="col-xs-12 col-sm-6 col-md-3".
Thus, a complete site can be expressed as a tree‑structured JSON containing all pages, sections, modules, and their configurations.
From Data to Site
Rendering the site involves traversing the JSON tree and rendering each node with a corresponding Vue component.
Write a component for each node type that receives
nodeand
themeColoras props.
Recursively render the tree by rendering a node and then its children.
Example of a simple paragraph component (Paragraph.vue):
<code><!-- Paragraph.vue -->
<template>
<div>
<h1 :style="{color: themeColor}">{{node.content.title}}</h1>
<small v-if="node.config.showSubTitle">{{node.content.subTitle}}</small>
<p>{{node.content.detail}}</p>
</div>
</template>
<script>
export default {
name: 'paragraph',
props: ['node', 'themeColor']
}
</script>
</code>The renderer component uses Vue’s dynamic
componentto select the appropriate component based on
node.type:
<code><!-- render.vue -->
<template>
<component :is="node.type" :node="node" :theme="themeColor">
<render v-for="child in node.children" :key="child.id" :node="child" :theme="themeColor" />
</component>
</template>
<script>
// Import all node components
import Page from './Page.vue'
import Section from './Section.vue'
import Paragraph from './Paragraph.vue'
export default {
name: 'render',
props: ['node', 'themeColor'],
components: { Page, Section, Paragraph }
}
</script>
</code>If Vue’s built‑in dynamic component is unavailable, the same effect can be achieved with
createElement(see the linked gist).
Editing and Saving
Creating a site involves selecting a template, adjusting the theme color, dragging modules, editing module content/configuration, and finally persisting the JSON tree.
Select a template → initializes the tree with default colors and modules.
Change theme color → updates
site.config.themeColor.
Drag a module into a section → pushes a new node into
section.children.
Reorder modules → updates the index within
section.children.
Edit module content/config → modifies the node’s
contentand
config.
Save site → stores the JSON tree in a database.
State management is handled with Vuex. A simple store definition:
<code>// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: { site: {} },
mutations: {
changeThemeColor () {},
addModule () {},
sortModule () {},
removeModule () {},
updateModule () {}
},
actions: {
getSite () {},
saveSite () {}
}
})
</code>To automatically persist changes, a Vuex plugin watches the
sitestate and dispatches
saveSiteon any modification:
<code>// store.js
const autoSave = (store) => {
store.watch(
state => state.site,
(newV, oldV) => {
store.dispatch('saveSite')
},
{ deep: true }
)
}
const store = new Vuex.Store({
state: { site: {} },
plugins: [autoSave]
})
</code>For editing, each module gets a corresponding edit component that wraps the display component, allowing inline or modal editing. The edit component adds an
edit-prefix to the node type, enabling the renderer to switch between read‑only and editable modes.
Drag‑and‑drop is implemented with
Vue.Draggable(a Vue wrapper for Sortable.js). The component listens to
addand
sortevents and calls the appropriate Vuex mutations.
Conclusion
The article walks through requirement analysis, data modeling, and Vue.js implementation for a drag‑and‑drop site‑builder, emphasizing how Vue.js handles data‑to‑view rendering and reactive updates, allowing developers to focus on business logic.
Baixing.com Technical Team
A collection of the Baixing.com tech team's insights and learnings, featuring one weekly technical article worth following.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.