motivation
Development dynamic form before related applications and visual designer, but found that if from a development perspective, no matter how rich functions, provide to the interface implementation more flexible and diversified designer would need to be further extended to add more features, so that the designer function becomes more and more bloated, learning to increase the cost of using the designer, More complex interaction requirements are often not as easy to implement as direct code development
Visual editing is generally only applicable to people who are not specialized in development work, and the requirements of interface interaction logic are highly reusable, which cannot cover all requirements for complex application scenarios, especially highly customized situations
The data defined in some configuration files is in YAML format, which seems to be easier to edit than json data, so WE decided to explore whether yamL data can be used to directly build the interface to reduce development costs
And then there’s the 1024 days of the year. Instead of going to all sorts of weird games and dinners, how can you be more pragmatic about the issues at hand
Results the preview
Based on the concept of “VJDesign – Vue interface Visual Designer”, we first developed a yamL editor demo, which can theoretically support any HTML element and any component library developed based on VUE3, currently integrated with the Element-Plus component library
Jrender-plus.net lify.app/ is an exploratory stage, and some features will be updated later
Since the project uses tailwindcss, some native HTML components such as input h1 will have no border and no default style when there is no style defined. As long as the style is added separately, it will not affect the function
Rule description
The definition rules are sorted out based on the sample demo
Basic attributes
The root level of yamL data definition includes datasource Fields
A datasource is a datasource. The data on the current interface can be defined here
Listeners are used to monitor and respond to changes in data
Fields is the definition of an interface layout
attribute | type | describe |
---|---|---|
datasource | object | Provides data and HTTP access |
listeners | array | Listen for property changes and perform response actions |
fields | array | Interface Layout Definition |
Using the component
Each node is a component, and the basic properties of the component are defined
attribute | type | describe |
---|---|---|
component | string | Component name, which can be the name of any HTML element or VUE component |
props | object | Component properties, which can be attributes of HTML elements or VUE components, are defined in HTML or VUE component library documents |
children | array | Collection of subcomponents |
Example 1: Show a layer with a border
fields:
- component: div
props:
style:
width: 200px
border: 1px solid red
children:
- component: p
props:
innerText: content
Copy the code
Effect:
Example 2: Vue Slot feature
Supports setting slot properties in components to render into the corresponding slots of VUE components
fields:
- component: el-input
model: model.text
props:
style:
width: auto
children:
- component: span
slot: append
props:
innerText: append
Copy the code
Effect:
The data source
Datasource refers to the data available in the current interface and is defined in datasource. Resources include objects, object attributes and remotely acquired data. Currently, object supports only defined data, while fetch can initiate HTTP requests to obtain remote data or send data to services
Object data source
An object is defined as follows: In the datasource, an object is defined. The properties of each object are the properties that the object can use on the interface. The value of each object can be an object, an array, or a value
datasource:
rawdata:
props:
prop1: aaa
prop2: 123
listdata:
props:
- name: sel1
value: 1
- name: sel2
value: 2
Copy the code
Data source definitions can be used directly in component property expressions as follows
fields:
- component: p
props:
innerText: $:rawdata.prop1
- component: p
props:
innerText: $:rawdata.prop2
Copy the code
Model is the default data source, which is an object that can be assigned as model.< property > in a property expression
Fetch the data source
Fetch data source is used to provide HTTP request capability, so that data can be fetched from the server or sent to the service. Data source Type is defined as FETCH, which represents a FETCH data source
The PROPS property of the FETCH data source is defined as follows
attribute | type | instructions |
---|---|---|
url | string | Requesting a resource URL |
autoLoad | boolean | Whether to automatically request after the interface is loaded |
type | string | The data typejson text |
props | object | Options for HTML native fetch |
defaultData | array/object | Default data after no request or request failure |
data | array/object | The result data of a request is usually only used in an expression |
The FETCH data source also has methods
methods | instructions |
---|---|
clear | Empty data |
fetch | The initiating |
Example 1: Request and display list data
datasource:
tabledata:
type: fetch
props:
url: /data/table.json
autoLoad: true
type: json
props:
method: GET
fields:
- component: div
children:
- component: el-button
props:
innerText: reload
onClick: | $:() => { tabledata.clear() tabledata.fetch() } - component: el-table
props:
data: $:tabledata.data
children:
- component: el-table-column
props:
label: name
prop: name
- component: el-table-column
props:
label: remark
prop: remark
Copy the code
Effect:
Listening to the
Listeners are defined in listeners to respond to changes in data and trigger actions. Currently, they can listen to object properties in the data source (consider whether to add component ref to support direct listening to component properties)
Monitoring is generally defined as follows:
listeners:
# listen on one
- watch: $:model.num
actions:
- handler: $() = > console.log(model.num)
# listen on multiple
- watch:
- $:model.text
actions:
- handler: $() = > console.log('xxx')
- handler: $() = > tabledata.fetch()
condition: $:model.text && model.text.length = = = 5
Copy the code
The attributes of a listener include the following
attribute | type | instructions |
---|---|---|
watch | array/object | The data to listen for |
actions | array | Triggered behavior |
deep | boolean | The depth of the listening |
immediate | boolean | Execute immediately after the interface is loaded |
The definition of an action action in Actions includes the following properties
attribute | type | instructions |
---|---|---|
handler | function | Behavior execution method |
condition | boolean | The condition that triggers the operation |
timeout | number | Method execution delays (which may be less common) |
The condition and timeout attributes can be replaced by the handler, but they should be kept in place for the sake of writing less code
Function extension
Using expressions
Any property in a component, data source, or listener can use expressions for data linkage
@model.value:arguments[0] #: text ${content} #: text ${content} #: text ${content} In fact, one form is sufficient for the expression
Using $:< content > to represent an expression, attributes defined in this form are parsed into program implementations, either as property values or as arrow functions by writing $:() => {} directly
Example 1: Attribute value linkage
fields:
- component: input
props:
style:
border: 1px solid silver
value: $:model.text
onInput: $:(e)=>model.text=e.target.value
- component: p
props:
innerText: $:model.text
Copy the code
Effect:
Example 2: Implement click events
Can pass before the attribute value added in yaml | to input line of text, more complex script can write a new line
fields:
- component: button
props:
style:
border: 1px solid
padding: 0.75rem 1.25rem
innerText: show
onClick: | $:() => { alert('message') }Copy the code
Effect:
functions
Function functions, similar to the formula functions in Excel that provide some common function operations in expressions, now support defining an object containing methods in the data source that can be used in expressions, so common functions are less important
There is only one case to note here. In the js expression aa.bb.cc.dd = value, an error is reported if the parent property is empty or undefined. Therefore, the SET function is provided to SET the value in depth
fields:
- component: input
props:
value: $:model.obj.text
onInput: $:(e) = > SET(model, 'obj.text'. e.target.value)
- component: p
props:
innerText: $:model.obj.text
Copy the code
Simplified null judgment logic
Quick properties
Extensions that integrate several commonly used properties further simplify component definitions
Example 1: VUE component V-Model
Component values are displayed and updated by associating the modelValue attribute and responding to the Update :modelValue event, using the Model attribute to simplify the definition
fields:
- component: el-input
model: model.text
props:
style:
width: auto
- component: p
props:
innerText: $:model.text
Copy the code
Example 2: Simplified element-Plus form item definition
Define the formItem attribute on the component to automatically add el-form-item to the outer layer
fields:
- component: el-form
props:
labelWidth: 120px
children:
- component: el-input
model: model.text
formItem:
label: input1
props:
style:
width: auto
- component: p
formItem:
label: display
props:
innerText: $:model.text
Copy the code
Effect:
Example 3: Control element show hide
Sets the condition attribute to control whether the element is displayed
fields:
- component: el-switch
model: model.checked
- component: p
condition: $:model.checked
props:
innerText: hello!!
Copy the code
Effect:
The for loop
Example 1: For loop display
When elements need to be looping over array data, you can use the for attribute
datasource:
listData:
props:
- aaaaa
- bbbbb
- ccccc
fields:
- component: ul
children:
- component: li
The second parameter name is not currently supported
for: item in listData
props:
innerText: $:`${item} - ${index}`
Copy the code
Other features
You just have to be more imaginative and experiment, and you can discover more characteristics
Example 1: Add a style tag and set the style
fields:
- component: span
props:
class: custom
innerText: Custom styles in the page
- component: style
props:
innerText: | .custom { color: red }Copy the code
Example 2: Define methods in datasource with expressions and associate them with drop-down lists
datasource:
list:
props:
- m1
- m2
methods:
props:
m1: $() = > { Alert (' 1 'output) }
m2: $() = > { Alert (' output 2 ') }
fields:
- component: el-select
model: model.sel
children:
- component: el-option
for: item in list
props:
value: $:item
label: $:item
- component: el-button
props:
onClick: $:methods[model.sel]
innerText: Choose to perform
Copy the code
Effect:
Rely on
library | version |
---|---|
vue | ^ 3.2.20 |
monaco-editor | ^ 0.29.1 |
monaco-yaml | ^ 3.2.1 |
file-saver | ^ at 2.0.5 |
js-yaml | ^ 4.1.0 |
element-plus | ^ 1.1.0 – beta. 24 |
ejs | ^ 3.1.6 |
about
scalability
The sample was developed using an extensible design pattern similar to the previous VJDesign-VUE interface visual designer, referring to the exported interface code
// Global extensions that are enabled for any rendering component in the current environment
useGlobalRender(
({ onBeforeRender, onRender, addDataSource, addFunction, addComponent }) = > {
// Expression parsing is not done before the current component is rendered
onBeforeRender(() = > (field, next) = > {
next(field)
})
// The expression has been parsed before the current component is rendered
onRender(() = > (field, next) = > {
next(field)
})
// Add a type of data source
addDataSource("Type".(options) = > {})
// Add a function function
addFunction("Name".(callback) = > (. args) = > {})
// Add support for custom components (not globally registered in vue only for current rendering, not globally registered)
addComponent("Name", Component)
}
)
// Root-level extensions, which use extensions registered with this method and are enabled for any rendering component in the child node
useRootRender(
({ onBeforeRender, onRender, addDataSource, addFunction, addComponent }) = > {
// ...})Copy the code
Can you export vUE components directly
Also consider this problem before, but the vue components quite so already compiled good static files, each component were fixed compiled component types and attributes, because components in the solution for each component before rendering preprocessing, process the results may be change the component’s attribute values, or change to render the component directly and change the child elements, The final output could be an implementation similar to This thing is still under study, but I am too busy to have much time now. If you have new ideas and suggestions, you can follow up again and see the response level For example, by defining an interface with YAML that is reused as a component in another interface, or by defining a slot in a component where the children element is exported to slot (the slot feature of Vue), So far we’ve implemented it all but we haven’t figured out how to do it in the editor yet