Form solution for complex scenes

Time:2020-10-24

Form solution for complex scenes

Source: https://unsplash.com/

Author: Dong Jianhua

1. Background

There are many b-end business scenarios of cloud music. Compared with C-end business, b-end business has a longer product life cycle and pays more attention to the carding of scenarios. Most of the time, the development of B-side business is to copy the previous code, which increases a lot of repetitive and boring workload.

In fact, the middle and back-end system can be divided into several common scenarios: forms, tables and charts. Forms involve complex scenarios such as linkage, verification, layout, etc., which are often the points that developers need to expend energy to solve.

Compared with the traditional ant design form development method, we think there are the following problems:

  1. First of all, the code can’t be serialized, and it’s more common for some non front-end developersJSONMethod to describe the form, because it is simple enough
  2. The validation of the form is not combined with the validation status
  3. onChangeIn the case of complex linkage, the code will become difficult to maintain, and it is easy to produce a lot of linked list logic
  4. Forms have many mutually exclusive states that can be sorted out, and we hope users can easily switch between these states
  5. For some common and common scenarios, such as form list, a set of feasible schemes can also be extracted

Therefore, although the traditional form development method is flexible enough, I still think that there is still room for optimization of forms, and I have made some trade-offs between flexibility and efficiency.

There are also mature form solutions in the outside world, such as formliy and formrender. Although some of the above problems have been solved, they are still not comprehensive enough. We need to have our ownstyleThe project.

Therefore, in order to improve the efficiency of the middle and back-end development, so that the front-end can invest time in more meaningful things, we summarized a set of form solutions for complex scenarios.

2. Technical scheme

The most important link in the technical solution is the schema design. The framework and architecture are all implemented around this link, so I will follow this idea to introduce to you.

2.1 schema design

Form scheme based onAnt DesignDevelopment, throughJSONMode, but it is notJSON SchemaThe outside world is based onJSON SchemaIn fact, we have also considered the configuration scheme ofJSON SchemaIt’s a bit cumbersome to write, so yesJSON SchemaAs an additional capability only.

The example is shown in the following code. The simplest form field is as long as it is configuredkeytypeandui.labelIt’s OK

const schema = [
    {
        "key": "name",
        "type": "Input",
        "ui": {
            "Label": "name"
        }
    },
    {
        "key": "age",
        "type": "InputNumber",
        "ui": {
            "Label": age
        },
        "props": {
            "Holder": "please enter age"
        }
    },
    {
        "key": "gender",
        "type": "Radio",
        "value": "male",
        "ui": {
            "Label": Gender    
        },
        "options": [
            {
                "Name": "male",
                "value": "male"
            },
            {
                "Name": "female",
                "value": "female"
            }
        ]
    }
];

export default function () {
    const formRef = useRef(null);

    const onSubmit = () => {
        formRef.current.submit().then((data: any) => {
            console.log(data);
        });
    };

    const onReset = () => {
        formRef.current.reset();
    };

    return (
        <>
            <XForm
                ref={formRef}
                schema={schema}
                labelCol={{ span: 6 }}
                wrapperCol={{ span: 12 }}
            />
            <div>
                < button type = "primary" onclick = {onsubmit} > submit < / button >
                < button onclick = {onreset} > Reset < / button >
            </div>
        </>
    );
}

Because the solution is based onAnt DesignOfFormComponent design, so in order to retainAnt DesignSome of the characteristics of the designuiandpropsThe two fields correspond to each otherForm.ItemOfpropsAnd componentsprops。 Even after thatAnt DesignSome functions or features are added to the form, and the form scheme can also be supported seamlessly.

2.1.1 calibration mode

Since the form is based onAnt DesignImplementation, then the verification also uses its verification class library async validator, this class library has been relatively mature and powerful, can verifyArrayandObjectAnd other deep-level data types, to meet the needs of complex verification, so we directly make adjustments based on this library.

adoptrulesField, except forasync-validatorIn addition to the original features, there are additional featuresstatus(check status) andtrigger(trigger conditions) are enumerated as follows:

  • Status: check status

    • Error (default): error
    • Warning: warning
  • Trigger: trigger condition

    • Submit (default): triggered when submitting
    • Change: trigger judgment when the value changes
    • Blur: trigger judgment when losing focus

The basic usage is as follows:

{
    "key": "name",
    "type": "Input",
    "ui": {
        "Label": "name"
    },
    "rules": [
        {
            "required": true,
            "Message": "name required",
            "trigger": "blur",
            "status": "error"
        }
    ]
}

2.1.2 linkage mode

In addition to verification, linkage is also a more commonly used function. The traditional linkage is through componentsonChangeWhen the linkage logic is more complex, it is as troublesome to look at the code as to search the linked list, so this paper designs aReverse listeningIn this way, all changes of the field are maintained in the field configuration itself, reducing the maintenance cost in the later stage.

adoptlistenersField to configure the designwatch(monitoring)condition(conditions)set(setting) the combination of three fields realizes the linkage function.

watchRecord the fields that need to be monitored. When there is any change in the listening fields, it will be triggeredconditionThe judgment of condition will be triggered only if the conditional judgment is passedsetset up.

[
    {
        "key": "name",
        "type": "Input"
    },
    {
        "key": "gender",
        "type": "Radio",
        "value": "male",
        "options": [
            {
                "Name": "male",
                "value": "male"
            },
            {
                "Name": "female",
                "value": "female"
            }
        ],
        "listeners": [
            {
                "watch": [ "name" ],
                "condition": "name.value === 'Marry'",
                "set": {
                    "value": "female"
                }
            }
        ]
    }
]

In the above example, when the name is Mary, the gender is adjusted to female by default.

2.1.3 form status

We found that some linkage scenarios are used to hide and display fields. In order to facilitate the user to switch states, the four mutually exclusive form states are organized into onestatusField:

  • Status: Status

    • Edit (default): Edit
    • Disabled: Disabled
    • Preview: Preview
    • Hidden: hidden

previewThe status is not provided by the component itself, but there are many requirements for preview. Therefore, we made an extension to preset the preview status for all basic form components. Even custom components display field values by default, and provide a solution if you need to handle them yourself.

The usage is as follows:

[
    {
        "key": "edit",
        "type": "Input",
        "status": "edit",
        "Value": editing,
        "ui": {
            "Label": Edit
        }
    },
    {
        "key": "disabled",
        "type": "Input",
        "status": "disabled",
        "Value": disable,
        "ui": {
            "Label": "disable"
        }
    },
    {
        "key": "preview",
        "type": "Input",
        "status": "preview",
        "Value": preview,
        "ui": {
            "Label": Preview
        }
    },
    {
        "key": "hidden",
        "type": "Input",
        "status": "hidden",
        "Value": hidden,
        "ui": {
            "Label": hide
        }
    }
]

The effect picture is as follows:

Form solution for complex scenes

2.1.4 options settings

Many selection components useoptionsField setting options, which are sometimes obtained through an asynchronous interface. Considering the asynchronous interface, four schemes are designed

  1. optionsbyArrayThe situation
{
    "key": "type",
    "type": "Select",
    "options": [
        {
            "Name": vegetables,
            "value": "vegetables"
        },
        {
            "Name": fruit,
            "value": "fruit"
        }
    ]
}
  1. optionsbystringInterface link
{
    "key": "type",
    "type": "Select",
    "options": "//api.test.com/getList"
}
  1. optionsbyobjectIn the case of,actionFor the interface link,namePropertyto configurenameField,valuePropertyto configurevalueField,pathTo get the option path,watchConfigure listening fields
{
    "key": "type",
    "type": "Select",
    "options": {
        "action": "//api.test.com/getList?name=${name.value}",
        "nameProperty": "label",
        "valueProperty": "value",
        "path": "data.list",
        "watch": [ "name" ]
    }
}
  1. actionbyfunctionThe situation
{
    "key": "type",
    "type": "Select",
    "options": {
        "action": (field, form) => {
            return fetch('//api.test.com/getList')
                .then(res => res.json());
        },
        "watch": [ "name" ]
    }
}

2.1.5 form list

A form list is a combined type of form, usually withTableandCardThere are two kinds of scenes, with the function of adding and deleting.

This type of form value is defined asArrayIn the form of return, so designedArrayComponents, according toprops.typeYesTableandCardThis is not the case,childrenConfigure the sub form and use it as follows:

{
    "key": "array",
    "type": "Array",
    "ui": {
        "Label": form list
    },
    "props": {
        "type": "Card"
    },
    "children": [
        {
            "key": "name",
            "type": "Input",
            "ui": {
                "Label": "name"
            }
        },
        {
            "key": "age",
            "type": "InputNumber",
            "ui": {
                "Label": age
            }
        },
        {
            "key": "gender",
            "type": "Radio",
            "ui": {
                "Label": Gender
            },
            "options": [
                {
                    "Name": "male",
                    "value": "male"
                },
                {
                    "Name": "female",
                    "value": "female"
                }
            ]
        }
    ]
}

The effect picture is as follows:

2.2 framework

Form solution for complex scenes

Around the schema design idea, we adopt the distributed management scheme, which separates the core layer from the rendering layer, and maintains the field information in the core layer. The rendering layer is only responsible for rendering, so as to separate the data and interface code.

Between the core layer and the render layerSub/PubThe rendering layer communicates by listening to a series of core layer definitionsEventEvent to adjust the interface.

This change of data state drives the change of interface. It is widely used in most frameworks. The advantages are as follows:

  1. Data and state sharing among fields of aspect
  2. By controlling the events, the rendering times can be optimized reasonably and the performance can be improved
  3. It can adapt to the situation of multi framework, only need to reuse a set of core layer code

The core layer is mainly composed ofFormFieldListenerManagerValidatoroptionManagerThe components are shown in the figure below

Form solution for complex scenes

amongFormIt’s the prototype of the form. There are a lot of themFieldField prototype fromListenerManagerThe function of unified management linkage,FieldThere areValidatorandOptionManagerManage checksums separatelyoptionsOption function

2.2.1 verification implementation

Mainly throughasync-validatorClass library implementation, but still can not meet the situation of multi check state and multi trigger conditions, so on this basis, we do some expansion, package into aValidatorClass.

Validatoronly oneValidator.validateMethod, passing atriggerParameters, instantiationValidatorWhen will go to analyzerulesField, according totriggerClassify and create the correspondingasync-validatorexample.

2.2.2 linkage realization

ListenerManagerhaveListenerManager.addMethods andListenerManager.triggerMethods for parsing and addinglistenersFields andFieldTrigger linkage effect when field changes.

The specific process is initializationFieldWhen thelistenersFields pass throughlistenerManager.addMethods the information was analyzed according to thewatchMediumkeyValues are classified and stored in them whenFieldWhen the information changes, it will pass throughListenerManager.triggerTrigger linkage, judgmentconditionWhether the condition is met, and if it is met, it will be triggeredsetContent.

2.2.3 form list implementation

The form list is actually composed of multipleXFormEach auto increment is an instanceXFormInstance, so linkage can only be carried out on the same line, and cross row linkage is not allowed.

When you click the Add button, thechildrenProvidedSchemaTemplate create aXFormexample:

Form solution for complex scenes

2.2.4 layout implementation

exceptAnt DesignIt also provides three more flexible ways to meet the needs of layout.

Layout is a real headache, especiallySchemaSimilar to inJSONIt is easy to cause complex layout under the structure ofSchemaDeep nesting, which we don’t want to see.

The initial solution is through grid layout, through the settingsFormOfrow.countperhapscol.countParameters calculate the number of rows and columns of the grid, and then distribute the fields. This method is only applicable to the case that the number of rows and columns is consistent, but this method is difficult to meet the situation that the number of rows and columns is inconsistent

Form solution for complex scenes

So we redesigned oneui.groupnameField, the samegroupnameAll fields of thedivWrapped, anddivOfclassNameNamelygroupnameUsers can write their own style to achieve complex layout, which is simple but practical.

3. Detail design

3.1 ignore specific field values

Some scenarios need to be ignoredstatusbyhiddenSo we designed aignoreValuesField, field configuration can be as follows:

  • Hidden: ignore the case with the status hidden
  • Preview: ignore the status of preview
  • Disabled: ignores the condition that the status is disabled
  • Null: ignores cases where the value is null
  • Undefined: ignores cases where the value is undefined
  • False like: ignore the case where the value = = false

Through configurationignoreValuesField, which is returned after submittingvaluesThe corresponding fields are ignored:

<XForm schema={schema} ignoreValues={['hidden', 'null']}/>

3.2 field deconstruction and reorganization

Field deconstruction refers to splitting the value of a field into multiple fields, and field reorganization refers to combining multiple fields into one field. The specific function of this field has not been realized, but preliminary ideas have been put forward.

Examples of field deconstruction are as follows, mainly throughkeySplit the field and finally returnvaluescontainstartTimeandendTimeTwo fields:

{
    "key": "[startTime, endTime]",
    "type": "RangePicker",
    "ui": {
        "Label": time selection
    }
}

It is found that many scenarios need to be composed of multiple fields to form a field. In this case, user-defined components need to be written. Otherwise, the data needs to be processed later. In order to simplify this process, the function of field reorganization is designed. adoptCombineThe component reconstructs multiple fields into one field

{
    "key": "time",
    "type": "Combine",
    "ui": {
        "Label": time selection
    },
    "props": {
        "shape": "{startTime, endTime, type}"
    },
    "children": [
        {
            "key": "startTime",
            "type": "DatePicker"
        },
        {
            "key": "endTime",
            "type": "DatePicker"
        },
        {
            "key": "type",
            "type": "Select",
            "options": [
                {
                    "Name": "release time,",
                    "value": "publishTime"
                },
                {
                    "Name": online time,
                    "value": "onlineTime"
                }
            ]
        }
    ]
}

4. Ending

The process of perfecting the form product is also a process of learning from others’ strong points. We investigated the competitive products in the industry, combined with their own business needs, and developed this product. The idea and implementation of the form scheme are described above for your reference. Unfortunately, our product has not been open source yet. I believe we will meet you at the right time.

5. Relevant information

  • Formily
  • FormRender

This article is released from Netease cloud music front-end team. It is forbidden to reprint it in any form without authorization. We recruit front-end, IOS and Android all year round. If you are going to change jobs and you happen to like cloud music, please join us grp.music -fe(at) corp.netease.com !

Recommended Today

Consul1.7 multi data center new hashicorp Learning Guide

More services, more problems With the popularity of Internet distributed system and microservice, how to improve the scalability and scalability between services? How to minimize the impact on dependent services when the server makes changes? How does the client develop the service without knowing? How to reduce configuration modification and overload nginx services behind the […]