Issue #1436Opened September 18, 2018by peakrams3 reactions

[QUESTION] Atomic blocks or complex components

Question

Hi,

I'm studying the editor and I think I understand the difference between components and blocks. Now, I was wondering if GrapesJS offers the possibily to manage a block as an atomic entity or permits to define components as a tree with of nodes.

For example, I have a block called separator that basically is a table with a tbody, a tr and a td that are all components in gjs. I would like that the click on any component of this separator block in reality selects the table component at which I setup custom traits and properties that I then can split programmatically on the correct component of the block (e.g. setting the height on the table would in reality set the height of the nested td).

My aim is to give the users the feeling that they are dealing with a single atomic element even if at the source code level the properties they set for that element are distributed among the different nodes that compose that element.

The main issue that I can foresee is that the source code should be importable so as it is generated by the editor and I'm not sure if the editor can distinguish the separator element from any td component of a table component used for tabular data and if it can sniff all the styles to repopulate the traits and the properties in the editor.

Is such a thing possible?

Thank you.

Answers (3)

artfSeptember 20, 20181 reactions

The main issue that I can foresee is that the source code should be importable so as it is generated by the editor and I'm not sure if the editor can distinguish the separator element from any td component of a table component used for tabular data and if it can sniff all the styles to repopulate the traits and the properties in the editor.

Yeah this is a good question, basically, the editor has a stack of component types and every time you add a new one it's placed on top of the stack

[
youLastAddedComponent,
...
table,
image,
text,
default,
]

Let's say you need to add a table component inside the selected one (Component's API)

const selected = editor.getSelected(); 
// selected is the Component's model
selected.append(`<table>...</table>`);

Now when you pass the HTML, the string is parsed and each Node is translated in a component object (at the moment, just a JS object) and once appended to selected's inner components (selected.components().add({ ...component object... })) it becomes the Component. But to distinguish exactly which type of model is the Node, you iterate over the stack, execute on each type isComponent static method and check if it's valid (you generally return an object like { type: 'my-type', ... }) So isComponent method here is the key, there you can implement the correct logic to make the editor understand if it's a normal table or the separator one (the method takes the Node as the argument so you just use normal DOM's API for the logic). You can also skip the recognition part by setting the type explicitly, in this way:

<table data-gjs-type="separator">...</table>

Be sure to check also the guide on how to correctly store and load templates, because if you use the same HTML, stored from the editor, for the editing, it's WRONG, you have to use the JSON data (which already contains all the info about your types, so you just skip the parsing and the recognition).

artfSeptember 20, 20181 reactions

the fact that it has returned a type for this node prevents the editor from testing the inner nodes of this node for a component type?

No, inner nodes will be still traversed, but you can actually prevent that with this as a return to your isComponent

isComponent(el) {
	... 
		return {
			type: 'separator',
			// If the returned object contains `components`, 
			// inner nodes will be skipped
			components: [],
		}
}

but, with this current setting you're telling that you have no components, so you'd actually get this rendered in you canvas:

<table ...></table>

empty table, so what you can do?!? With this:

components: [el.innerHTML],

it's the same like not putting components, because the string will be parsed and processed again. What you can actually do, what I think might be your case:

return {
			type: 'separator',
			content: el.innerHTML,
			components: [],
		}

content is for static stuff, the editor won't create Components and you won't even be able to select them (not being a Component you don't have the model)

artfSeptember 20, 20181 reactions

Yeah, the content property could really solve my problem but I guess I have to manually keep it's value in sync (with string manipulation) with the changes made by the users to the properties and the traits of the separator component, and, since it is part of the model of a separator component, any change to it will trigger a view update, right?

Well as you said I would like that the click on any component of this separator block in reality selects the table I supposed that the separator was something static, without the need to change inner stuff, it depends what your traits are doing, eg. if you just change attributes of the component itself it's still in sync with its view

Otherwise, if I let the inner nodes to be traversed but check the ancestors and/or the descendants to establish if e.g. the they are part of my separator component and make them not droppable, not draggable and not selectable (maybe defining separated components like separator-tr, separator-td, etc.)? Could this be a better solution?

Yeah sure

Related Questions and Answers

Continue research with similar issue discussions.

Paid Plugins That Match This Issue

Curated by issue keywords and label relevance to help you ship faster.

View all plugins

Loading paid plugin recommendations...

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.