This is the first serialized analysis of the visual Editor Gaea-Editor, and I hope I can explain the motivation behind the creation of this web Editor in a limited space, as well as its potential for good use. It will have the following characteristics:

  1. Running on a web page
  2. Document flow layout, absolute positioning supported at the same time
  3. Any React component you insert can be dragged directly into the page as an edit element
  4. React-native compatible Web components allow it to generate Native pages for Android and ios
  5. Have the Gaea-Preview suite and pass in the JSON generated by gaea-Editor to instantly generate pages
  6. Gaea-web-components gaEA-native Components provide minimum granularity components of web pages and native bases respectively
  7. You can customize any React component to insert into the editor
  8. As flexible as Chrome-DevTools, you can sort and drag elements of any edit area across hierarchies
  9. You can customize the combination of templates, three times five to two to solve similar needs

Of course, after reading this article, I am not limited to understanding the functions of this editor, I will introduce its design details in great detail, as long as you read it carefully, you can make your own web editor ^_^.

Before I talk about the visual editor, I have to mention React, which was my motivation for creating it. Although it is not sure how long React will be popular, the componentization it brings has set off an industrial revolution in the front industry. Of course, the concept of componentization was not initiated by React, but React greatly reduced the cost of componentization, just like the invention of moving-type printing, which popularized books that only aristocrats could afford.

In universal modular era, I wrote a few articles to introduce how to use and management module: www.jianshu.com/p/aaca5047a… And component library maintenance experience: github.com/fex-team/fi… . Componentization is now more and more popular, we mastered the component after the law of the development and management, project organization structure, team collaboration has made rapid progress, to promote the efficiency of modular bring also dwindling, but visual editing may be a way to break through the bottleneck, first, on the basis of a ready-made components, The cost of moving it to a visual editing platform is very low, and second, the page development outside of the code is much more intuitive, with some of the code helping to make the structure more efficient (similar to The Unity engine).

React combines with native drag and drop

The first and most important step in the web editor is the drag-and-drop function, and we want the final result to look like this:




drag.gif

As shown in the figure, support for arbitrary drag, drag animation, drag across the parent level. We can do this using SorTableJS. This article focuses on how to combine React.

Use the sortable. Js

To drag and drop support nested, we use the development version installation address “sortablejs” : “git://github.com/RubaXa/Sortable.git#dev”

React/sortable/sortable/react/sortable/react/sortable/react

  • Sortable operates smoothly because the DOM structure is changed during the drag and drop process, but the dom nodes generated as a result are not controlled by React
  • Sortable deletes the previously dragged node. As a result, the React Diff algorithm finds that the DOM has disappeared when it deletes elements

To sum up, sortable should operate DOM, but dom operations should not be separated from react control. We adopted the operation playback mode to roll back the DOM modification after sortable operation, and then refresh the operation result status with React.

Right Menu Configuration

Configure the menu on the right as follows:

Sortable. Create (ReactDOM. FindDOMNode (enclosing dragContainerInstance), {/ / in a group, can drag across the group group: {name: 'gaea-layout', pull: 'clone', put: false }, sort: false, delay: 0, onStart: (event: Any) => {// Store where the drag starts and where the drag ends //... }, onEnd: (event: Any) => {// When you drag the menu, the real element will be dragged away, // Remove the clone element first (parentNode) // Remove the parentNode (parentNode). If (event.clone.parentNode) {// If (event.clone.parentNode) { Instructions have been dragged away by real. DragContainerDomInstance. RemoveChild (event. Clone) / / to get really move past the back an if (this. LastDragStartIndex = = = Enclosing dragContainerDomInstance..childnodes. Length) {/ / if the drag is the last one enclosing dragContainerDomInstance. The appendChild (event. Item)} Else {/ / drag and drop is not a final enclosing dragContainerDomInstance. The insertBefore (event item, This. DragContainerDomInstance..childnodes [this lastDragStartIndex])}} else {/ / not dragged away, just shake, need not tube}}})Copy the code

As detailed in the code comments above, drag from the menu should be configured in pull:clone mode, so that the same element can be dragged more than once. Put :false prevents menus from being dragged in by other elements.

When the drag starts, save the position after the drag, so that the user can find the element, generate the instance on the page, and save the position before the drag, so that the element can be recovered after the drag ends.

If it is empty, it indicates that the element has not been dragged away, so no processing is needed. Otherwise, delete the Clone DOM left in the original position, because this element is not controlled by React, and then restore the actual dragged element to its previous position

View Area Configuration

The sortable configuration in the editor view area is long, so disassemble and analyze.

groupConfiguration:

group: {
    name: 'gaea-layout',
    pull: true,
    put: true
}Copy the code

This is easy to understand because elements in the view area can be removed or moved in by other elements, so pull and put are both true.

When you start dragging

OnStart: (event: any) => {// Save the position before and after the drag}Copy the code

When the drag is done

OnEnd: (event: any) => {// omitted}Copy the code

You don’t need to do anything special to end a drag, but you can do something visual, like tell the user that the drag is over.

When an element is added

onAdd: (event: Any)=> {// cancel srotable dom modification // delete dom element, Let the react to generate a dom if (this. Props. Viewport. CurrentMovingComponent. IsNew) {/ / is new drag in, there is no tube, because the toolbar will take it back / / why don't you delete? Because the element, whether it's clone or not, has been moved over, } else {// If it was moved from an element (new, not the same parent that changed the sort) // Return the element to the parent if it was dragged earlier (this. Props. Viewport. DragStartParentElement..childnodes. Length = = = 0) {/ / before there is only one element this.props.viewport.dragStartParentElement.appendChild(event.item) } else if (this. Props. Viewport. DragStartParentElement..childnodes. Length = = = this. Props. The viewport. The dragStartIndex) {/ / is the last position is the last one, And the parent element has multiple elements this. Props. Viewport. DragStartParentElement. The appendChild (event. Item)} else {/ / not the last one, And there are multiple elements / / inserted into it before the next element of an enclosing props. Viewport. DragStartParentElement. InsertBefore (event item, this.props.viewport.dragStartParentElement.childNodes[this.props.viewport.dragStartIndex]) } } }Copy the code

When an element is added, there are two ways to add an element, or drag it in from an existing element.

If the new element is dragged in from the toolbar, just re-render it using React.

If it is moved in from another view element, you need to restore the element to the position where it was dragged, thus reverting to the state before the sortable operation and rendering the two parent components with React.

When the location of an element within the same parent is updated

onUpdate: (event: Any)=> {// Change the dom of srotable with the same parent subelement // cancel the dom modification, OldIndex = event. OldIndex as number const newIndex = event. NewIndex as number if (this. Props. Viewport. DragStartParentElement..childnodes. Length = = = oldIndex + 1) {/ / began drag and drop from the last element This. Props. Viewport. DragStartParentElement. The appendChild (event. Item)} else {the if (newIndex > oldIndex) {/ / if moved to the back this.props.viewport.dragStartParentElement.insertBefore(event.item, This. Props. Viewport. DragStartParentElement..childnodes [oldIndex])} else {/ / if moved to the front this.props.viewport.dragStartParentElement.insertBefore(event.item, this.props.viewport.dragStartParentElement.childNodes[oldIndex + 1]) } } this.props.viewport.sortComponents(this.props.mapUniqueKey, event.oldIndex as number, event.newIndex as number) }Copy the code

We just need to restore the element position, simulate the element movement based on the starting and ending positions, and render with React. Here it needs to be noted that the drag of sortable is not a simple swap of A and B, but a -> B, as shown in the following diagram:




Paste_Image.png

As shown in the figure above, there are six elements in the same parent, and when we drag the first element to the fifth element, instead of 5, 2, 3, 4, 1, 6, the order becomes something like this:




Paste_Image.png

Inevitably, we swap elements one by one, then update the parent element’s child element’s position. The react diff algorithm automatically moves the DOM nodes instead of re-rendering the 1, 2, 3, 4, 5 child nodes.

When the elements are removed

OnRemove: (event: any)=> {// Render the parent element, reduce the child element in the current position}Copy the code

When the element is removed, it doesn’t fire onUpdate, it fires onAdd, but we’ve already put the element back in onAdd, so we don’t have to do anything here, we just update the React parent element and re-render it, Let React remove the element.

conclusion

Based on the above game of menu area and view area, sortable and React rendering are finally combined perfectly. However, there is no need to worry about any side effects, because we have restored all sortable operations, so we actually only use its drag process and drag results. React controls changes to DOM elements without actually changing any DOM structures.

We will continue to break down the implementation and include the warehouse address in the next series. Stay tuned for details on how to place elements in a view area and support infinite levels of nesting!