Dooringx demo Address: DooringX visual building platform Note: ⚠️ This article is the first signed article in the Nuggets community, and is not authorized to be reproduced

H5-dooring, a visual editor launched last year, has been in the works for a year now. During this period, we have received a lot of valuable suggestions from many interested netizens and big people. We are also working on the implementation step by step.

  • Code generation capability (i.e. source code download function)
  • Component interaction (that is, components support link jumps, popover interactions, custom events, etc., commonly used in business)
  • Data source management (that is, the ability for different user-created pages to share data, and for different components to share data)
  • Component stores (that is, the ability for users to produce components, define components, and access component data)
  • Layout ability (i.e. users can choose different layout schemes to design the page)
  • Common functions integration (page screenshots, wechat sharing, debug ability)

The above work requirements have been implemented in H5-dooring, as well as in my previous articles. However, in order to enable more people to have their own visual building system at a low cost, our team leaders have spent a lot of time on research and sedimentation. Recently, we have opened an open source visual building framework, DooringX-Lib, on which we can easily make visual editors without considering the details of internal implementation. I would like to share with you how to use and implement the visualization framework, and thank the dooring visualization team for their hard work.

Visualized basic use and technical implementation of the framework

In order to give you a better understanding of the visual framework, I give a few examples of images:

  1. Antd – antd – pro

We all know thatantdIs a popular front-end component library, then based on its upper encapsulation of the management backgroundantd-proThat’s the upper application of it.

  1. GrapesJS — craft. Js

GrapesJSIs a foreign page editor framework (see my previous article for details)This foreign open source framework lets you easily build your own page editor), thencraft.jsIt’s the upper application framework.

  1. Dooringx – lib – dooringx

Dooringx-lib is a visual scaffolding framework, and DooringX is a visual editor based on DooringX-Lib.

The reason why I want to introduce the difference between them is because there are many friends who don’t understand this concept very clearly before. After understanding the “connotation” of the visual framework, we start today’s core content.

1. The technology stack

Before sharing framework implementation approach to his name, of course, framework implementation ecological, we still adopt the familiar React mobile client component library using the Ann team zarm, editor application layer USES the antd, as for the other such as drag and drop, the reference line, state management, plug-in mechanism and so on are our team bosses from the research plan. If you are a VUE or other technology stack-based team, you can also refer to the implementation ideas, I believe will also have certain inspiration for you.

2. Basic usage

Let’s take a look at how to use the framework before we dive in. We just need to install it as follows:

npm/yarn  install dooringx-lib
Copy the code

At the same time, we also provide basic use demo, convenient for everyone to get started in their own projects quickly:

# Clone project
# cnpmjs
git clone https://github.com.cnpmjs.org/H5-Dooring/dooringx.git

# or
git clone https://github.com/H5-Dooring/dooringx.git


Enter the project directory
cd dooringx

# install dependencies
yarn install

Start the basic example
yarn start:example

# start dooringx - lib
yarn start

# Launch dooringX doc
yarn start:doc

yarn build
Copy the code

The Github project for Demo is as follows:

Github address: github.com/H5-Dooring/…

With that in mind, let’s take a look at the basic architecture and implementation ideas.

3. Dooringx-lib infrastructure and working mechanism

The diagram above is the architecture diagram I organized according to the current dooringX-Lib project architecture, which basically contains most of the necessary modules for building the editing framework. In order to ensure the flexibility of the framework, we can also install corresponding functional components on demand, develop custom components, etc. Here is a basic import example:

import {
    RightConfig,
    Container,
    useStoreState,
    innerContainerDragUp,
    LeftConfig,
    ContainerWrapper,
    Control,
} from 'dooringx-lib';
Copy the code

We split the framework into modules that are independent of each other and can relate to each other. The complete workflow is as follows:

As you can see from the above figure, we only need basic business development capabilities to build our own building platform with DooringX-Lib, which is like the essence of any program: data and logic.

4. Dooringx-lib plug-in development

Next I will share with you how to develop dooringX-Lib plug-ins and how to implement them (how to import plug-ins, how to write components, how to register functions, etc.). If you are interested, you can also practice with the following methods.

4.1 How Can I Import Components

We can see in the figure above that on the left is our component material area, which is divided into basic components, media components and visual components. Their addition will be uniformly managed in LeftRegistMap array, and their basic structure is as follows:

const LeftRegistMap: LeftRegistComponentMapItem[] = [
  {
      type: 'basic'.// Component category
      component: 'button'.// Component name
      img: 'icon-anniu'./ / component icon
      displayName: 'button'.// The component's Chinese name
      urlFn: () = > import('./registComponents/button'),  // Register a callback},];Copy the code

The left component supports synchronous or asynchronous imports.

If you need to import the component asynchronously, you need to fill in the urlFn and need a function that returns a Promise. Remote loading of components can also be supported, as long as WebPack is attached.

If components need to be imported synchronously, they need to be placed in the initComponentCache of the configuration item so that they are registered in The componentRegister at load time.

initComponentCache: {
  modalMask: { component: MmodalMask },  
},
Copy the code

4.2 How Can I Customize the Left Panel

Just pass in the leftRenderListCategory for the left panel.

leftRenderListCategory: [
  {
type: 'basic'.icon: <HighlightOutlined />,
displayName: 'Base components'}, {type: 'xxc'.icon: <ContainerOutlined />,
custom: true.customRender: <div>I am custom rendering</div>],},Copy the code

Type is the category in which the left component is displayed. Icon is the small icon on the left (as shown in the figure above). When custom is true, you can use customRender to customize the render.

4.3 Develop a custom visual component

The component needs to export an object generated by ComponentItemFactory:

const MButton = new ComponentItemFactory(
	'button'.'button',
	{
style: [
	createPannelOptions<FormMap, 'input'> ('input', {
		receive: 'text'.label: 'words',})],animate: [createPannelOptions<FormMap, 'animateControl'> ('animateControl', {})],
actions: [createPannelOptions<FormMap, 'actionButton'> ('actionButton'}, {, {})],props: {...text:'x.dooring'// input Initial value received by the configuration item component}},(data, context, store, config) = > {
return <ButtonTemp data={data} store={store} context={context} config={config}></ButtonTemp>;
	},
	true
);

export default MButton;
Copy the code

The first parameter is the component registration name, and the second parameter is used to demonstrate usage.

The third parameter configures the configuration item component on the right panel. The key is the classification of the right panel, and the value is the configuration item component array.

The fourth parameter configures the initial value of the component. In particular, the component must have an initial width height (not supported by the content), otherwise it will cause problems when adapting to select all.

This initial value has a number of useful properties, such as fixed for fixed positioning, which can be changed in conjunction with configuration items to make the component fixed.

There is also canDrag, which is similar to the lock command. The locked element cannot be dragged.

Rotate in the original value requires an object, value represents the rotation Angle, and canRotate represents whether rotation can be performed. (supported since version 0.7.0)

The fifth parameter is a function in which you receive the configuration item from the receive attribute (for now, the default configuration item is receive). For example, if you receive text, the data field in the function will receive this field.

Context is usually available only with Preview and Edit for context determination.

Config can get all the data and use it to make events.

The sixth argument, resize, is used to determine whether scaling can be done. When it is false, scaling cannot be done.

The seventh parameter needPosition, when some components are moved to the canvas, the drag point will be adopted by default. This configuration item defaults to true, which is the position to be dragged, and false, which will be placed using the top and left positions of the component itself.

4.4 Event Registration

  1. Registration time

Events can be subdivided into registration timing and functions. The registration timing can be realized by hook in the component:

useDynamicAddEventCenter(pr, `${pr.data.id}-init`.'Initial render timing'); // Register name must have id convention!
useDynamicAddEventCenter(pr, `${pr.data.id}-click`.'Click execution time');
Copy the code

The first argument to useDynamicAddEventCenter is an object composed of the four arguments to Render. The second parameter is the timing name of the registration, which must be associated with the ID. This is the convention, otherwise multiple components may cause name conflicts and make it easier to find the timing.

After registering the time, we need to place the time in the corresponding trigger position, for example, the button click execution time is placed in onclick:

<Button
    onClick={()= > {
eventCenter.runEventQueue(`${pr.data.id}-click`, pr.config);
    }}
>
    x.dooring
</Button> 
Copy the code

The first parameter is the registered timing name and the second is the last parameter in the Render function, config

  1. Function registered

Functions are thrown by the component and can be loaded onto the event chain. For example, if I register a function that changes text, I can call that function at any time in the component and trigger it to change text.

Function registration needs to be put into useEffect, and the function needs to be uninstalled when the component is uninstalled! Otherwise, there will be more and more functions.

useEffect(() = > {
const functionCenter = eventCenter.getFunctionCenter();
const unregist = functionCenter.register(
	`${pr.data.id}+ change text function '.async (ctx, next, config, args, _eventList, iname) => {
		const userSelect = iname.data;
		const ctxVal = changeUserValue(
			userSelect['Change text data source'],
			args,
			'_changeval',
			config,
			ctx
		);
		const text = ctxVal[0]; setText(text); next(); [{},name: 'Change text data source'.data: ['ctx'.'input'.'dataSource'].options: {
				receive: '_changeval'.multi: false,},},]);return () = >{ unregist(); }; } []);Copy the code

The parameters and configuration of the function are described in function development below.

4.5 Development of the right Panel

To develop a custom right side property panel, we simply configure the component we are developing into an object and put it into initFormComponents. For a good development experience, we need to define a formMap type:

export interfaceFormBaseType { receive? :string;
}
export interface FormInputType extends FormBaseType {
    label: string;
}
export interface FormActionButtonType {}
export interface FormAnimateControlType {}
export interface FormMap {
    input: FormInputType;
    actionButton: FormActionButtonType;
    animateControl: FormAnimateControlType;
}
Copy the code

The formMap key name is the initFormComponents key name, and the formMap value corresponds to the value that the component needs to receive.

In the case of the input component, FormInputType has two attributes: label and receive.

When developing this component, props receives:

interface MInputProps {
    data: CreateOptionsRes<FormMap, 'input'>;
    current: IBlockType;
    config: UserConfig;
}
Copy the code

That is, data is of formMap type and current is the currently clicked component, not to mention config.

Remember the third parameter in component development on the left? So it’s all connected:

style: [
    createPannelOptions<FormMap, 'input'> ('input', {
        receive: 'text'.label: 'words'})].Copy the code

CreatePannelOptions fills the generic type of the function with the corresponding component, which will give the received configuration item a good hint.

All you need to do in the configuration item component is to receive the configuration item from the component and modify the current property:

function MInput(props: MInputProps) {
	const option = useMemo(() = > {
returnprops.data? .option || {}; }, [props.data]);return (
<Row style={{ padding: '10px 20px' }}>
	<Col span={6} style={{ lineHeight: '30px' }}>{(option as any)? . Label | | 'words'} :</Col>
	<Col span={18}>
            <Input
                value={props.current.props[(option as any).receive] || ''}
                onChange={(e)= >{ const receive = (option as any).receive; const clonedata = deepCopy(store.getData()); const newblock = clonedata.block.map((v: IBlockType) => { if (v.id === props.current.id) { v.props[receive] = e.target.value; } return v; }); store.setData({ ... clonedata, block: [...newblock] }); }} ></Input>
	</Col>
</Row>
	);
}
Copy the code

The data can be modified anywhere because it can be easily retrieved from the Store.

Associate the component’s value with the current property and onChange to modify store, thus completing a two-way binding.

Note: If your right-hand component needs properties other than blocks, you may need to determine if it is in popover mode.

4.6 Customizing the Right-click menu

Right-click menu can be customized:

// Custom right-click
const contextMenuState = config.getContextMenuState();
const unmountContextMenu = contextMenuState.unmountContextMenu;
const commander = config.getCommanderRegister();
const ContextMenu = () = > {
	const handleclick = () = > {
unmountContextMenu();
	};
	const forceUpdate = useState(0) [1];
	contextMenuState.forceUpdate = () = > {
forceUpdate((pre) = > pre + 1);
	};
	return (
<div
	style={{
            left: contextMenuState.left.top: contextMenuState.top.position: 'fixed',
            background: 'rgb(24.23.23)',
	}}
>
	<div
            style={{ width: '100%' }}
            onClick={()= > {
                    commander.exec('redo');
                    handleclick();
            }}
        >
            <Button>The custom</Button>
	</div>
</div>
	);
};
contextMenuState.contextMenu = <ContextMenu></ContextMenu>;
Copy the code

So I’m going to get contextMenuState, and on contextMenuState there’s unmountContextMenu which turns off the right click menu method. So you need to call close after clicking. Both left and top are the right – click locations. Additionally, we need to add a strong brush to the component, which is assigned to forceUpdate to follow the component as it moves.

4.7 Form Verification Submission Roadmap

There are many ways to submit a form validation because the data is all connected, or you can write a form component directly. When not using a form component, it is simple to have a validation function and a submission function for each input component. The validation is up to the user, and the input thrown allows the user to choose where to put the variables and the user to name them.

When the submit button is clicked, the verification function and submission function of all components are called to make them thrown to the context, and then aggregated into objects through the context aggregation function. Finally, the whole process can be completed by sending the function to the corresponding backend. We can try this demo out on DooringX.

Another option is to write a submit button with fixed parameters and some rules, such as specifying that all forms on the page will be collected and submitted.

Then we can use the data source to automatically submit all form output content to the data source, and the final submit button is extracted according to the key format specified by the data source and sent to the back end.

In the late planning

In the later stage, we will continue to iterate and optimize product functions. If you have good suggestions, please feel free to communicate with us, and you are also welcome to actively raise an issue on Github.

If you are interested in visual scaffolding or low code/zero code, please refer to my previous articles or share your thoughts in the comments section, and explore the real technology of the front end.

Making: dooringx | build visual drag-and-drop platform fast and efficient Starting: nuggets technology community team: Dooring visual team column: low code visualization public number: anecdotal stories front end

More recommended

  • How to design a visual platform component store?
  • Build engine from zero design visualization large screen
  • Build desktop visual editor Dooring from zero using Electron
  • (low code) Visual construction platform data source design analysis
  • Build a PC page editor pc-dooring from scratch
  • How to build building blocks to quickly develop H5 pages?