In the first step the HTML string is parsed and trasformed to what is called **Component Definition**, so the result of the input would be:
In the first step the HTML string is parsed and trasformed to what is called **Component Definition**, so the result of the input would be:
@ -76,9 +81,11 @@ You can notice the result is similar to what is generally called a **Virtual DOM
The meaning of properties like `tagName`, `attributes` and `components` are quite obvious, but what about `type`?! This particular property specifies the actual **Component** of our **Component Definition** (you check the list of default components [below](#built-in-components)) and if it's omitted, the default one will be used `type: 'default'`.
The meaning of properties like `tagName`, `attributes` and `components` are quite obvious, but what about `type`?! This particular property specifies the actual **Component** of our **Component Definition** (you check the list of default components [below](#built-in-components)) and if it's omitted, the default one will be used `type: 'default'`.
At this point, a good question would be, how the editor assignes those types by starting from a simple HTML string? This step is identified as **Component Recognition** and it's explained in detail in the next paragraph.
At this point, a good question would be, how the editor assignes those types by starting from a simple HTML string? This step is identified as **Component Recognition** and it's explained in detail in the next paragraph.
### Component Recognition and Component Type Stack
### Component Recognition and Component Type Stack
As we said before, when you pass an HTML string as a component to the editor, that string is parsed and compiled to the [Component Definition](#component-definition) with a new `type` property. To understand what `type` should be assigned, for each parsed HTML Element, the editor iterates over all the defined components, called **Component Type Stack**, and checks via `isComponent` method (we will see it later) if that component type is appropriate for that element. The Component Type Stack is just a simple array of components but the important part is the order of those components. Any new added Custom Component (we'll see later how to create them) goes on top of the Component Type Stack and each element returned from the parser iterates the stack from top to bottom (the last element of the stack is the `default` one), the iteration stops once one of the component returns a truthy value from the `isComponent` method.
As we said before, when you pass an HTML string as a component to the editor, that string is parsed and compiled to the [Component Definition](#component-definition) with a new `type` property. To understand what `type` should be assigned, for each parsed HTML Element, the editor iterates over all the defined components, called **Component Type Stack**, and checks via `isComponent` method (we will see it later) if that component type is appropriate for that element. The Component Type Stack is just a simple array of component types but what is matter is the order of those types. Any new added custom **Component Type** (we'll see later how to create them) goes on top of the Component Type Stack and each element returned from the parser iterates the stack from top to bottom (the last element of the stack is the `default` one), the iteration stops once one of the component returns a truthy value from the `isComponent` method.
SVG - ComponentTypeStack
SVG - ComponentTypeStack
@ -87,7 +94,8 @@ If you're importing big chunks of HTML code you might want to improve the perfor
:::
:::
### Component Creation
### Component instance
Once the **Component Definition** is ready and the type is assigned, the [Component](api/component) instance can be created (known also as the **Model**). Let's step back to our previous example with the HTML string, the result of the `append` method is an array of added components.
Once the **Component Definition** is ready and the type is assigned, the [Component](api/component) instance can be created (known also as the **Model**). Let's step back to our previous example with the HTML string, the result of the `append` method is an array of added components.
@ -135,15 +143,16 @@ JSON.stringify(component)
For storing/loading all the components you should rely on the [Storage Manager](modules/storage)
For storing/loading all the components you should rely on the [Storage Manager](modules/storage)
:::
:::
The Component instance is responable for the **final data** (eg. HTML, JSON) of your templates, so if you need, for example, to update/add some attribute in the HTML you need to update its component (eg. `component.addAttributes({ title: 'Title added' })`), so the Component/Model is your **Source of Truth**.
So, the **Component instance** is responable for the **final data** (eg. HTML, JSON) of your templates. If you need, for example, to update/add some attribute in the HTML you need to update its component (eg. `component.addAttributes({ title: 'Title added' })`), so the Component/Model is your **Source of Truth**.
### Component Rendering
### Component rendering
Another important thing of components is how they are rendered in the **canvas**, this aspect is handled by the **View** of the component. It has nothing to do with the **final data**, you can return a big `<div>...</div>` string as HTML of your component but render it as a simple image in the canvas (think about placeholders for complex/dynamic data).
Another important part of components is how they are rendered in the **canvas**, this aspect is handled by the **View** of the component. It has nothing to do with the **final data**, you can return a big `<div>...</div>` string as HTML of your component but render it as a simple image in the canvas (think about placeholders for complex/dynamic data).
So, by default, the view of components is automatically synced with the data of its models (you can't have a View without a Model). If you update the attribute of the component or append a new one as a child, the view will render it in the canvas.
So, by default, the view of components is automatically synced with the data of its models (you can't have a View without a Model). If you update the attribute of the component or append a new one as a child, the view will render it in the canvas.
Unfotunatelly, sometimes, you might need some additional logic to handle better the component result. Think about allowing a user build its `<table>` element, for this specific case you might want to add custom buttons in the canvas, so it'd be easier adding/removing columns/rows. To handle those cases you can rely on the View, where you can add additional DOM component, attach events, etc. All of this will be completely unrelated with the final HTML of the `<table>` (the result the user would expect) as it handled by the Model.
Unfotunatelly, sometimes, you might need some additional logic to handle better the component result. Think about allowing a user build its `<table>` element, for this specific case you might want to add custom buttons in the canvas, so it'd be easier adding/removing columns/rows. To handle those cases you can rely on the View, where you can add additional DOM component, attach events, etc. All of this will be completely unrelated with the final HTML of the `<table>` (the result the user would expect) as it handled by the Model.
Once the component is rendered (when you actually see it in the canvas) you can always access its View and the DOM element.
Once the component is rendered (when you actually see it in the canvas) you can always access its View and the DOM element.
So generally, the View is something you wouldn't need to change as the default one handles already the sync with the Model but in case you'd need more control over elements (eg. custom UI in canvas) you'll probably need to create a custom component and extend the default View with your logic. We'll see later how to create custom components.
So, generally, the View is something you wouldn't need to change as the default one handles already the sync with the Model but in case you'd need more control over elements (eg. custom UI in canvas) you'll probably need to create a custom component type and extend the default View with your logic. We'll see later how to create custom Component Types.
So far we have seen the core concept behind Components and how they work. The **Model/Component** is the **source of truth** for the final code of templates (eg. the HTML export relies on it) and the *View/ComponentView* is what is used by the editor to **preview our components** to users in the canvas.
So far we have seen the core concept behind Components and how they work. The **Model/Component** is the **source of truth** for the final code of templates (eg. the HTML export relies on it) and the *View/ComponentView* is what is used by the editor to **preview our components** to users in the canvas.
<!--
TODO
TODO
A more advanced use case of custom components is an implementation of a custom renderer inside of them
A more advanced use case of custom components is an implementation of a custom renderer inside of them
-->
## Built-in Components
## Built-in Component Types
Here below you can see the list of built-in components, ordered by their position in the Component Type Stack
Here below you can see the list of built-in component types, ordered by their position in the **Component Type Stack**
* [`cell`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableCell.js) - Component for handle `<td>` and `<th>` elements
* [`cell`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableCell.js) - Component for handle `<td>` and `<th>` elements
* [`row`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableRow.js) - Component for handle `<tr>` elements
* [`row`](https://github.com/artf/grapesjs/blob/dev/src/dom_components/model/ComponentTableRow.js) - Component for handle `<tr>` elements
@ -195,298 +206,430 @@ Here below you can see the list of built-in components, ordered by their positio
## Define new Component
## Define new Component Type
Now that we know how components work, we can start exploring the process of creating new **Component Types**.
<u>The first rule of defining new component types is to place the code inside a plugin</u>. This is necessary if you want to load your custom types at the beginning, before any component initialization (eg. a template loaded from DB). The plugin is loaded before component fetch (eg. in case of Storage use) so it's a perfect place to define component types.
```js
const myNewComponentTypes = editor => {
editor.DomComponents.addType(/* API for component type definition */);
};
const editor = grapesjs.init({
container : '#gjs',
// ...
plugins: [ myNewComponentTypes ],
});
```
Let's say we want to make the editor understand and handle better `<input>` elements. This is how we would start defining our new component type
```js
editor.DomComponents.addType('my-input-type', {
// Make the editor understand when to bind `my-input-type`
isComponent: el => el.tagName === 'INPUT',
Now that we know how components work, we can start exploring the process of creating new **Custom Components**.
// Model definition
model: {
defaults: {
tagName: 'input',
draggable: 'form, form *', // Can be dropped only inside `form` elements
droppable: false, // Can't drop other elements inside it
attributes: { // Default attributes
type: 'text',
name: 'default-name',
placeholder: 'Insert text here',
},
traits: [
'name',
'placeholder',
{ type: 'checkbox', name: 'required' },
],
}
}
});
```
Let's say we want to make the editor understand and handle better `<input>` elements
With this code the editor will be able to understand simple text `<input>`s, assign default attributes and show some trait for a better attribute handling.
First of all, place your components inside a plugin
::: tip
To understand better how Traits work you should read its [dedicated page](Traits.html) but we highly sugggest to read it after you've finished reading this one
:::
### isComponent
--- OLD
Let's see in detail what we have done so far. The first thing to notice is the `isComponent` function, we have mentioned it already in [this](#component-recognition-and-component-type-stack) section and we need it to make the editor understand `<input>` during the component recognition step.
It receives only the `el` argument, which is the parsed HTMLElement node and expects a truthy value in case the element satisfies your logic condition. So, if we add this HTML string as component
But now, how does the editor recognize which Component to bind to the `img` element and what to do with the `span` one?
The resultant Component Definition will be
Each Component inherits, from the base one, a particular static method
```js
```js
/**
{
* @param {HTMLElement} el
type: 'my-input-type',
* @return {Object}
attributes: {
*/
name: 'my-test',
isComponent: function(el) {
title: 'hello',
...
},
}
}
```
```
This method gives us the possibility to recognize and bind component types to each HTMLElement (div, img, iframe, etc.). Each **HTML string/element** introduced inside the canvas will be processed by `isComponent` of all available types and if it matches, the object represented the type should be returned. The method `isComponent`**is skipped** if you add the component object (`{ type: 'my-custom-type', tagName: 'div', attribute: {...}, ...}`) or declare the type explicitly on the element (`<divdata-gjs-type="my-custom-type">...</div>`)
If you need you can also customize the resultant Component Definition by returning an object as the result:
For example, with the image component this method looks like:
```js
```js
// Image component
editor.DomComponents.addType('my-input-type', {
isComponent: function(el) {
isComponent: el => {
if(el.tagName == 'IMG')
if (el.tagName === 'INPUT') {
return {type: 'image'};
// You should explicitly declare the type of your resultant
}
// object, otherwise the `default` one will be used
const result = { type: 'my-input-type' };
if (/* some other condition */) {
result.attributes = { title: 'Hi' };
}
return result;
}
},
// ...
});
```
```
Let's try with something that might look a little bit tricky. What about a Google Map?!? Google Maps are generally embedded as `iframe`s, but the template can be composed by a lot of different `iframe`s. How can I tell the editor that a particular iframe is actually a Google's Map? Well, you'll have to figure out the right pattern, you have the `HTMLElement` so you can make all the checks you want. In this particular case this pattern is used:
**Be aware** that this method will probably receive ANY parsed element from your canvas (eg. on load or on add) and not all the nodes have the same interface (eg. properties/methods).
In addition to `tagName` check, we also used the `src` property, but you can actually override it with your own logic by extending the built-in component.
You will see printing all the nodes, so doing something like this `el.getAttribute('...')` (which will work on the div but not on the text node), without an appropriate check, will break the code.
It's also important to understand that `isComponent` is executed only if the parsing is required (eg. by adding components as HTML string or initializing the editor with `fromElement`). In case the type is already defined, there is no need for the `isComponent` to be executed.
One more tip, if you define a component type without the `isComponent`, the only way for the editor to see that component will be with a declared type (via object like `{ type: '...' }` or using `data-gjs-type`)
## Define new Component
Let's see an example with another HTML element that is not handled by default Component types. What about `input` elements?
With the default GrapesJS configuration `input`s are treated like any other element; you can move it around, style it, etc. However, we'd like to handle this type of element more specifically. In this case, we have to create a new Component type.
### Model
Let's define few specs for our new *Input* type:
Now that we got how `isComponent` works we can start to explore the `model` property.
The `model` is probably the one you'll use the most as is what is used for the description of your component and the first thing you can see is its `defaults` key which just stands for *default component properties* and it reflects the already described [Component Definition](#component-definition)
* Can be dropped only inside `form` elements
The model defines also what you will see as the resultant HTML (the export code) and you've probably noticed the use of `tagName` (if not specified the `div` will be used) and `attributes` properties on the model.
* Can't drop other elements inside it
* Can change the type of the input (text, password, email, etc.)
* Can make it required for the form
To define a new Component type you need to choose from which built-in Component inherit its properties, in our case we just gonna choose the default one. Let's see a complete example of the new type definition
One another important property (not used because `<input/>` doesn't need it) might be `components`, which defines default internal components
```js
```js
// Get DomComponents module
defaults: {
var comps = editor.DomComponents;
tagName: 'div',
attributes: { title: 'Hello' },
// Get the model and the view from the default Component type
From the example above you can notice few interesting things: how to bind events, how to update directly the DOM and how to update the model. The difference between updating the DOM and the model is that the HTML code (the one you get with `editor.getHtml()`) is generated from the *Model* so updating directly the DOM will not affect it, it's just the change for the canvas.
You'll find other lifecycle methods, like `init`, [below](#lifecycle-hooks)
Now let's go back to our input component integration and see another useful part for the component customization
### View
## Update Component type
Generally, when you create a component in GrapesJS you expect to see in the canvas the preview of what you've defined in the model. Indeed, by default, the editor does the exact thing and updates the element in the canvas when something in the model changes (eg. attributes, tag, etc.) to obtain the classic WYSIWYG (What You See Is What You Get) experience. Unfortunately, not always the simpliest thing is the right one, by building components for the builder you will notice that sometimes you'll need something more:
* You want to improve the experience of editing of the component.
A perfect example is the TextComponent, its view is enriched with a built-in RTE (Rich Text Editor) which enables the user to edit the text faster by double clicking on it.
So you'll probably feel a need adding actions to react on some DOM events or even custom UI elements (eg. buttons) around the component.
* The DOM representation of the component acts differently from what you expect, so you need to change some behaviour.
An example could be a VideoComponent which, for example, is loaded from Youtube via iframe. Once the iframe is loaded, everything inside it is in a different context, the editor is not able to see it, indeed if you point your cursor on the iframe you'll interact with the video and not the editor, so you can't event select your component. To workaround this "issue", in the render, we disabled the pointer interaction with the iframe and wrapped it with another element (without the wrapper the editor would select the parent component). Obviosly, all of this changes has nothing to do with the final code, the result will always be a simple iframe
* You need to customize the content or fill it with some data from the server
Here an example of how easily you can update/override the component
For all of this cases you can use the `view` in your component type defintion. The input component is probably not the best use case for this scenario but we'll try to cover most of the cases with an example below
```js
```js
var originalMap = comps.getType('map');
editor.DomComponents.addType('my-input-type', {
// ...
model: {
// ...
},
view: {
// Be default, the tag of the element is the same of the model
tagName: 'div',
comps.addType('map', {
// Add easily component specific listeners with `events`
model: originalMap.model.extend({
// Being component specific (eg. you can't attach here listeners to window)
// Override how the component is rendered to HTML
// you don't need to care about removing them when the component is removed,
toHTML: function() {
// they will be managed automatically by the editor
return '<div>My Custom Map</div>';
events: {
click: 'clickOnElement',
// You can also make use of event delegation
// and listen to events bubbled from some inner element
'dblclick .inner-el': 'innerElClick',
},
},
}, {
isComponent: function(el) {
// ... new logic for isComponent
},
}),
view: originalMap.view
});
```
## Improvement over addType <Badgetext="0.14.50+"/>
innerElClick(ev) {
ev.stopPropagation();
// ...
Now, with the [0.14.50](https://github.com/artf/grapesjs/releases/tag/v0.14.50) release, defining new components or extending them is a bit easier (without breaking the old process)
// If you need you can access the model from any function in the view
this.model.components('Update inner components');
},
* If you don't specify the type to extend, the `default` one will be used. In that case, you just
// On init you can create listeners, like in the model, or start some other
use objects for `model` and `view`
// function at the beginning
* The `defaults` property, in the `model`, will be merged automatically with defaults of the parent component
init({ model }) {
* If you use an object in `model` you can specify `isComponent` outside or omit it. In this case,
// Do something in view on model property change
the `isComponent` is not mandatory but without it means the parser won't be able to identify the component
In the example above the editor will not get the new type from the HTML because the content is already parsed and appended, so it'll get it only with new components (eg. from Blocks)