diff --git a/docs/404.html b/docs/404.html index 4ebda1f33..6c4c8e475 100644 --- a/docs/404.html +++ b/docs/404.html @@ -8,12 +8,12 @@ - - - + + + -

404

That's a Four-Oh-Four.
Take me home.
- +

404

There's nothing here.
Take me home.
+ diff --git a/docs/Home.html b/docs/Home.html index 6bc603362..4a33202c6 100644 --- a/docs/Home.html +++ b/docs/Home.html @@ -8,9 +8,9 @@ - - - + + +

Check Storage Manager API Reference

Last Updated: 7/13/2018, 12:12:35 AM
- + diff --git a/docs/api/assets.html b/docs/api/assets.html index 6ed99896b..9908d561d 100644 --- a/docs/api/assets.html +++ b/docs/api/assets.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/api/canvas.html b/docs/api/canvas.html index fd2125b30..49c7ec5dc 100644 --- a/docs/api/canvas.html +++ b/docs/api/canvas.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/api/commands.html b/docs/api/commands.html index 3b7485fde..3d19c41d2 100644 --- a/docs/api/commands.html +++ b/docs/api/commands.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/api/component.html b/docs/api/component.html index 40e2e083d..4eee13d70 100644 --- a/docs/api/component.html +++ b/docs/api/component.html @@ -8,9 +8,9 @@ - - - + + +

Returns String HTML string

getId

Return the component id

Returns String

setId

Set new id on the component

Parameters

Returns this

getEl

Get the DOM element of the component. +

Returns String HTML string

getId

Return the component id

Returns String

setId

Set new id on the component

Parameters

Returns this

getEl

Get the DOM element of the component. This works only if the component is already rendered

Returns HTMLElement

getView

Get the View of the component. This works only if the component is already rendered

Returns ComponentView

onAll

Execute callback function on itself and all inner components

Parameters

Examples

component.onAll(component => {
  // do something with component
@@ -149,6 +149,6 @@ This method is used in Components.js just after the parsing

+ diff --git a/docs/api/components.html b/docs/api/components.html index fee9cf5e8..8c6d1ead8 100644 --- a/docs/api/components.html +++ b/docs/api/components.html @@ -8,9 +8,9 @@ - - - + + + - + diff --git a/docs/api/device_manager.html b/docs/api/device_manager.html index b3a7be957..5e64b1230 100644 --- a/docs/api/device_manager.html +++ b/docs/api/device_manager.html @@ -8,9 +8,9 @@ - - - + + +

- + diff --git a/docs/api/editor.html b/docs/api/editor.html index 1cd567d38..db89ec7e7 100644 --- a/docs/api/editor.html +++ b/docs/api/editor.html @@ -8,9 +8,9 @@ - - - + + +

Components

Blocks

Assets

Keymaps

Style Manager

Storages

Canvas

Selectors

RTE

Commands

General

getConfig

Returns configuration object

Parameters

Returns any Returns the configuration object or -the value of the specified property

getHtml

Returns HTML built inside canvas

Parameters

Returns string HTML string

getCss

Returns CSS built inside canvas

Parameters

Returns string CSS string

getJs

Returns JS of all components

Returns string JS string

getComponents

Return the complete tree of components. Use getWrapper to include also the wrapper

Returns Components

getWrapper

Return the wrapper and its all components

Returns Component

setComponents

Set components inside editor's canvas. This method overrides actual components

Parameters

Examples

editor.setComponents('<div class="cls">New component</div>');
+the value of the specified property

getHtml

Returns HTML built inside canvas

Parameters

  • opts

Returns string HTML string

getCss

Returns CSS built inside canvas

Parameters

  • optsObject Options (optional, default {}) +
    • opts.avoidProtectedBoolean Don't include protected CSS (optional, default false)

Returns string CSS string

getJs

Returns JS of all components

Returns string JS string

getComponents

Return the complete tree of components. Use getWrapper to include also the wrapper

Returns Components

getWrapper

Return the wrapper and its all components

Returns Component

setComponents

Set components inside editor's canvas. This method overrides actual components

Parameters

Examples

editor.setComponents('<div class="cls">New component</div>');
 // or
 editor.setComponents({
  type: 'text',
@@ -132,6 +133,6 @@ To get more about this feature read: 
+    
   
 
diff --git a/docs/api/index.html b/docs/api/index.html
index f1732c847..191d48957 100644
--- a/docs/api/index.html
+++ b/docs/api/index.html
@@ -8,9 +8,9 @@
     
   
   
-
-    
-    
+    
+    
+    
   
   
     
- + diff --git a/docs/api/keymaps.html b/docs/api/keymaps.html index 153f62967..552acfd17 100644 --- a/docs/api/keymaps.html +++ b/docs/api/keymaps.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/api/storage_manager.html b/docs/api/storage_manager.html index 4099dd0a3..5e7b34351 100644 --- a/docs/api/storage_manager.html +++ b/docs/api/storage_manager.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/guides/Custom-CSS-parser.html b/docs/guides/Custom-CSS-parser.html index a421f63ea..bb810f586 100644 --- a/docs/guides/Custom-CSS-parser.html +++ b/docs/guides/Custom-CSS-parser.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/guides/Replace-Rich-Text-Editor.html b/docs/guides/Replace-Rich-Text-Editor.html index c077d7c8a..ebf0eaba8 100644 --- a/docs/guides/Replace-Rich-Text-Editor.html +++ b/docs/guides/Replace-Rich-Text-Editor.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/index.html b/docs/index.html index 200ae5542..c12df356f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Assets.html b/docs/modules/Assets.html index 5063fcede..94fd942a1 100644 --- a/docs/modules/Assets.html +++ b/docs/modules/Assets.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Blocks.html b/docs/modules/Blocks.html index 1c11375dc..ec5233369 100644 --- a/docs/modules/Blocks.html +++ b/docs/modules/Blocks.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Commands.html b/docs/modules/Commands.html index 9d8a9eea4..94e86ef5a 100644 --- a/docs/modules/Commands.html +++ b/docs/modules/Commands.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Components-new.html b/docs/modules/Components-TOREMOVE.html similarity index 63% rename from docs/modules/Components-new.html rename to docs/modules/Components-TOREMOVE.html index cb1c7b731..0d640e52f 100644 --- a/docs/modules/Components-new.html +++ b/docs/modules/Components-TOREMOVE.html @@ -8,9 +8,9 @@ - - - + + +
GitHub -

Component Manager

The Component is the base element for template composition. It is atomic, so elements like images, text boxes, maps, etc. fit the definition of a Component. The concept of the component was made to allow the developer to bind different behaviors to different elements. Like for example, opening the Asset Manager on double click of the image.

WARNING

This guide is referring to GrapesJS v0.14.67 or higher

How Components work?

Let's see in detail how components work by looking at all steps from adding an HTML string to the editor.

This is how we can add new components to the canvas:

// Append components directly to the canvas
-editor.addComponents(`<div>
-  <img src="https://path/image" />
-  <span title="foo">Hello world!!!</span>
-</div>`);
-
-// or into some, already defined, component.
-// For instance, appending to a selected component would be:
-editor.getSelected().append(`<div>...`);
-
-// Actually, editor.addComponents is an alias of...
-editor.getWrapper().append(`<div>...`);
-

TIP

If you need to append a component in a specific position, you can use at option. To add a component on top of all others (in the same collection) you would use

component.append('<div>...', { at: 0 })
-

or in the middle

const { length } = component.components();
-component.append('<div>...', { at: parseInt(length / 2, 10) })
-

Component Definition

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:

{
-  tagName: 'div',
-  components: [
-    {
-      type: 'image',
-      attributes: { src: 'https://path/image' },
-    }, {
-      tagName: 'span',
-      type: 'text',
-      attributes: { title: 'foo' },
-      components: [{
-        type: 'textnode',
-        content: 'Hello wdsforld!!!'
-      }]
-    }
-  ]
-}
-

The real Component Definition would be a little bit bigger so we reduced the JSON for the sake of simplicity.

You can notice the result is similar to what is generally called a Virtual DOM, a lightweight rappresentation of the DOM element. This actually helps the editor to keep track of the state of our elements and make performance-friendly changes/updates. -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) 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.

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 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.

SVG - ComponentTypeStack

TIP

If you're importing big chunks of HTML code you might want to improve the performances by skipping the parsing and the component recognition steps by passing directly Component Definiton objects or using the JSX syntax. Read more about it here...TODO

Component Creation

Once the Component Definition is ready and the type is assigned, the 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.

const component = editor.addComponents(`<div>
-  <img src="https://path/image" />
-  <span title="foo">Hello world!!!</span>
-</div>`)[0];
-

The Component instance contains properties and methods which allows you to obtain its data and change them. -You can read properties with the get method, like, for example, the type

const componentType = component.get('type'); // eg. 'image'
-

and to update properties you'd use set, which might change the way a component behavies in the canvas.

// Make the component not draggable
-component.set('draggable', false);
-

You can also use methods like getAttributes, setAttributes, components, etc.

const innerComponents = component.components();
-// Update component content
-component.components(`<div>Component 1</div><div>Component 2</div>`);
-

Each component can define its own properties and methods but all of them will always extend, at least, the default one (then you will see how to create new custom components and how to extend the already defined) so it's good to check the Component API to see all available properties and methods.

The main purpose of the Component is to keep track of its data and to return them when necessary. One common thing you might need to ask from the component is to show its current HTML

const componentHTML = component.toHTML();
-

This will return a string containing the HTML of the component and all of its children. -The component implements also toJSON methods so you can get its JSON structure in this way

JSON.stringify(component)
-

TIP

For storing/loading all the components you should rely on the Storage Manager

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.

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).

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. -Once the component is rendered (when you actually see it in the canvas) you can always access its View and the DOM element.

const component = editor.getSelected();
-// Get the View
-const view = component.getView();
-// Get the DOM element
-const el =  component.getEl();
-

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 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 -A more advanced use case of custom components is an implementation of a custom renderer inside of them

Built-in Components

Here below you can see the list of built-in components, ordered by their position in the Component Type Stack

Define new Component

Now that we know how components work, we can start exploring the process of creating new Custom Components.

Let's say we want to make the editor understand and handle better <input> elements

First of all, place your components inside a plugin

--- OLD

Component recognition

But now, how does the editor recognize which Component to bind to the img element and what to do with the span one? +

Component Manager

The Component is the base element for template composition. It is atomic, so elements like images, text boxes, maps, etc. fit the definition of a Component. The concept of the component was made to allow the developer to bind different behaviors to different elements. Like for example, opening the Asset Manager on double click of the image.

Built-in components

  • default (Basic)
  • wrapper
  • text
  • textnode
  • svg
  • script
  • image
  • video
  • label
  • link
  • map
  • table
  • row (for the table)
  • cell (for the table)

How Components work?

When we pass an HTML string to the editor like this:

<div>
+  <img src="https://path/image" />
+  <span title="foo">bar</span>
+</div>
+

For each DOM element (div, img, span, etc.) the editor will create and store an object representation. Every future change to the template will be made on top of this structure, which will then reflect on the canvas. So each object, usually called Model (or state/store), will be the source of truth for the template, but what exactly does that mean?

In more practical example, once the template is rendered on the canvas, if you try to remove one of its elements (eg. by using the browser inspector) and ask the editor to print the HTML (using editor.getHtml()) you'll see that the element will still be there. This is because the editor relies on Models and not on the DOM elements inside the canvas. This approach allows us to be extremely flexible on how we generate the final code (from the Model) and how to render it inside the canvas (from the View).

Manage Components

Component recognition

But now, how does the editor recognize which Component to bind to the img element and what to do with the span one? Each Component inherits, from the base one, a particular static method

/**
  * @param {HTMLElement} el
  * @return {Object}
@@ -102,7 +50,7 @@ Each Component inherits, from the base one, a particular static method

return {type: 'map', src: el.src}; } }, -

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.

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 inputs 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.

Let's define few specs for our new Input type:

  • Can be dropped only inside form elements
  • 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

// Get DomComponents module
+

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.

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 inputs 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.

Let's define few specs for our new Input type:

  • Can be dropped only inside form elements
  • 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

// Get DomComponents module
 var comps = editor.DomComponents;
 
 // Get the model and the view from the default Component type
@@ -355,7 +303,7 @@ editor.on// after all new types
   editor.render();
 </script>
-

Solution 2: put all the stuff inside a plugin (Creating plugins)

Last Updated: 8/13/2019, 5:17:26 PM
- +

Solution 2: put all the stuff inside a plugin (Creating plugins)

Last Updated: 9/17/2019, 2:00:02 AM
+ diff --git a/docs/modules/Components-js.html b/docs/modules/Components-js.html index 3b310bbab..da3b6f40e 100644 --- a/docs/modules/Components-js.html +++ b/docs/modules/Components-js.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Components.html b/docs/modules/Components.html index e7e99bb0b..96908495f 100644 --- a/docs/modules/Components.html +++ b/docs/modules/Components.html @@ -8,9 +8,9 @@ - - - + + +
GitHub -

Component Manager

The Component is the base element for template composition. It is atomic, so elements like images, text boxes, maps, etc. fit the definition of a Component. The concept of the component was made to allow the developer to bind different behaviors to different elements. Like for example, opening the Asset Manager on double click of the image.

Built-in components

How Components work?

When we pass an HTML string to the editor like this:

<div>
-  <img src="https://path/image" />
-  <span title="foo">bar</span>
-</div>
-

For each DOM element (div, img, span, etc.) the editor will create and store an object representation. Every future change to the template will be made on top of this structure, which will then reflect on the canvas. So each object, usually called Model (or state/store), will be the source of truth for the template, but what exactly does that mean?

In more practical example, once the template is rendered on the canvas, if you try to remove one of its elements (eg. by using the browser inspector) and ask the editor to print the HTML (using editor.getHtml()) you'll see that the element will still be there. This is because the editor relies on Models and not on the DOM elements inside the canvas. This approach allows us to be extremely flexible on how we generate the final code (from the Model) and how to render it inside the canvas (from the View).

Manage Components

Component recognition

But now, how does the editor recognize which Component to bind to the img element and what to do with the span one? -Each Component inherits, from the base one, a particular static method

Component Manager

The Component is a base element of the template. It might be something simple and atomic like an image or a text box, but also complex structures, more probably composed by other components, like sections or pages. The concept of the component was made to allow the developer to bind different behaviors to different elements. For example, opening the Asset Manager on double click of the image is a custom behavior binded to that particular type of element.

WARNING

This guide is referring to GrapesJS v0.15.8 or higher

How Components work?

Let's see in detail how components work by looking at all the steps from adding an HTML string to the editor.

TIP

All the following snippets can be run directly in console from the main demo

This is how we can add new components to the canvas:

// Append components directly to the canvas
+editor.addComponents(`<div>
+  <img src="https://path/image" />
+  <span title="foo">Hello world!!!</span>
+</div>`);
+
+// or into some, already defined, component.
+// For instance, appending to a selected component would be:
+editor.getSelected().append(`<div>...`);
+
+// Actually, editor.addComponents is an alias of...
+editor.getWrapper().append(`<div>...`);
+

TIP

If you need to append a component in at a specific position, you can use at option. So, to add a component on top of all others (in the same collection) you would use

component.append('<div>...', { at: 0 })
+

or in the middle

const { length } = component.components();
+component.append('<div>...', { at: parseInt(length / 2, 10) })
+

Component Definition

In the first step, the HTML string is parsed and transformed to what is called Component Definition, so the result of the input above would be:

{
+  tagName: 'div',
+  components: [
+    {
+      type: 'image',
+      attributes: { src: 'https://path/image' },
+    }, {
+      tagName: 'span',
+      type: 'text',
+      attributes: { title: 'foo' },
+      components: [{
+        type: 'textnode',
+        content: 'Hello wdsforld!!!'
+      }]
+    }
+  ]
 }
-

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 isComponentis skipped if you add the component object ({ type: 'my-custom-type', tagName: 'div', attribute: {...}, ...}) or declare the type explicitly on the element (<div data-gjs-type="my-custom-type">...</div>)

For example, with the image component this method looks like:

// Image component
-isComponent: function(el) {
-  if(el.tagName == 'IMG')
-    return {type: 'image'};
+

The real Component Definition would be a little bit bigger so so we'd reduced the JSON for the sake of simplicity.

You might notice the result is similar to what is generally called a Virtual DOM, a lightweight representation of the DOM element. This actually helps the editor to keep track of the state of our elements and make performance-friendly changes/updates. +The meaning of properties like tagName, attributes and components are quite obvious, but what about type?! This particular property specifies the Component Type of our Component Definition (you check the list of default components below) and if it's omitted, the default one will be used type: 'default'. +At this point, a good question would be, how the editor assigns 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

As we mentioned before, when you pass an HTML string as a component to the editor, that string is parsed and compiled to the 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.

TIP

If you're importing big string chunks of HTML code you might want to improve the performances by skipping the parsing and the component recognition steps by passing directly Component Definition objects or using the JSX syntax. +Read here about how to setup JSX syntax parser

Component instance

Once the Component Definition is ready and the type is assigned, the 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.

const component = editor.addComponents(`<div>
+  <img src="https://path/image" />
+  <span title="foo">Hello world!!!</span>
+</div>`)[0];
+

The Component instance contains properties and methods which allows you to obtain its data and change them. +You can read properties with the get method, like, for example, the type

const componentType = component.get('type'); // eg. 'image'
+

and to update properties you'd use set, which might change the way a component behavies in the canvas.

// Make the component not draggable
+component.set('draggable', false);
+

You can also use methods like getAttributes, setAttributes, components, etc.

const innerComponents = component.components();
+innerComponents.forEach(comp => console.log(comp.toHTML()));
+// Update component content
+component.components(`<div>Component 1</div><div>Component 2</div>`);
+

Each component can define its own properties and methods but all of them will always extend, at least, the default one (then you will see how to create new custom components and how to extend the already defined) so it's good to check the Component API to see all available properties and methods.

The main purpose of the Component is to keep track of its data and to return them when necessary. One common thing you might need to ask from the component is to show its current HTML

const componentHTML = component.toHTML();
+

This will return a string containing the HTML of the component and all of its children. +The component implements also toJSON methods so you can get its JSON structure in this way

JSON.stringify(component)
+

TIP

For storing/loading all the components you should rely on the Storage Manager

So, the Component instance is responsible 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

Another important part of components is how they are rendered in the canvas, this aspect is handled by its View. It has nothing to do with the final HTML 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).

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.

Unfortunately, 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 you can always access its View and the DOM element.

const component = editor.getSelected();
+// Get the View
+const view = component.getView();
+// Get the DOM element
+const el =  component.getEl();
+

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.

Built-in Component Types

Here below you can see the list of built-in component types, ordered by their position in the Component Type Stack

  • cell - Component for handle <td> and <th> elements
  • row - Component for handle <tr> elements
  • table - Component for handle <table> elements
  • thead - Component for handle <thead> elements
  • tbody - Component for handle <tbody> elements
  • tfoot - Component for handle <tfoot> elements
  • map - Component for handle <a> elements
  • link - Component for handle <a> elements
  • label - Component for handle properly <label> elements
  • video - Component for videos
  • image - Component for images
  • script - Component for handle <script> elements
  • svg - Component for handle SVG elements
  • comment - Component for comments (might be useful for email editors)
  • textnode - Similar to the textnode in DOM definition, so a text element without a tag element.
  • text - A simple text component that can be edited inline
  • wrapper - The canvas need to contain a root component, a wrapper, this component was made to identify it
  • default - Default base component

Define Custom Component Type

Now that we know how components work, we can start exploring the process of creating custom Component Types.

The first rule of defining new component types is to place the code inside a plugin. 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.

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

editor.DomComponents.addType('my-input-type', {
+  // Make the editor understand when to bind `my-input-type`
+  isComponent: el => el.tagName === 'INPUT',
+
+  // Model definition
+  model: {
+    // Default properties
+    defaults: {
+      tagName: 'input',
+      draggable: 'form, form *', // Can be dropped only inside `form` elements
+      droppable: false, // Can't drop other elements inside
+      attributes: { // Default attributes
+        type: 'text',
+        name: 'default-name',
+        placeholder: 'Insert text here',
+      },
+      traits: [
+        'name',
+        'placeholder',
+        { type: 'checkbox', name: 'required' },
+      ],
+    }
+  }
+});
+

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.

TIP

To understand better how Traits work you should read its dedicated page but we highly suggest to read it after you've finished reading this one

isComponent

Let's see in detail what we have done so far. The first thing to notice is the isComponent function, we have already mentioned its usage in this 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

// ...after editor initialization
+editor.addComponents(`<input name="my-test" title="hello"/>`)
+

The resultant Component Definition will be

{
+  type: 'my-input-type',
+  attributes: {
+    name: 'my-test',
+    title: 'hello',
+  },
 }
-

Let's try with something that might look a little bit tricky. What about a Google Map?!? Google Maps are generally embedded as iframes, but the template can be composed by a lot of different iframes. 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:

// Map component
-isComponent: function(el) {
-	if(el.tagName == 'IFRAME' && /maps\.google\.com/.test(el.src)) {
-		return {type: 'map', src: el.src};
-	}
+

If you need you can also customize the resultant Component Definition by returning an object as the result:

editor.DomComponents.addType('my-input-type', {
+  isComponent: el => {
+    if (el.tagName === 'INPUT') {
+      // 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;
+    }
+  },
+  // ...
+});
+

WARNING

Keep the isComponent function as simple as possible

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). +If you do this:

// ...
+// Print elements
+isComponent: el => {
+    console.log(el);
+    return el.tagName === 'INPUT';
 },
-

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.

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 inputs 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.

Let's define few specs for our new Input type:

  • Can be dropped only inside form elements
  • 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

// Get DomComponents module
-var comps = editor.DomComponents;
-
-// Get the model and the view from the default Component type
-var defaultType = comps.getType('default');
-var defaultModel = defaultType.model;
-var defaultView = defaultType.view;
-
-var inputTypes = [
-  {value: 'text', name: 'Text'},
-  {value: 'email', name: 'Email'},
-  {value: 'password', name: 'Password'},
-  {value: 'number', name: 'Number'},
-];
-
-// The `input` will be the Component type ID
-comps.addType('input', {
-  // Define the Model
-  model: defaultModel.extend({
-    // Extend default properties
-    defaults: Object.assign({}, defaultModel.prototype.defaults, {
-      // Can be dropped only inside `form` elements
-      draggable: 'form, form *',
-      // Can't drop other elements inside it
-      droppable: false,
-      // Traits (Settings)
-      traits: ['name', 'placeholder', {
-          // Change the type of the input (text, password, email, etc.)
-          type: 'select',
-          label: 'Type',
-          name: 'type',
-          options: inputTypes,
-        },{
-          // Can make it required for the form
-          type: 'checkbox',
-          label: 'Required',
-          name: 'required',
-      }],
-    }),
+
+// ...
+editor.addComponents(`<div>
+  I'm a text node
+  <!-- I'm a comment node -->
+  <img alt="Image here"/>
+  <input/>
+</div>`);
+

You will see printing all the nodes, so doing something like this el.getAttribute('...') in your isComponent (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. +Let's see some examples:

// isComponent will be executed on some-element
+editor.addComponents('<some-element>...</some-element>');
+
+// isComponent WON'T be executed on OBJECTS
+// If the object has no `type` key, the `default` one will be used
+editor.addComponents({
+  type: 'some-component',
+});
+
+// isComponent WON'T be executed as we're forcing the type
+editor.addComponents('<some-element data-gjs-type="some-component">...');
+

If you define the Component Type without using isComponent, the only way for the editor to see that component will be with an explicitly declared type (via an object { type: '...' } or using data-gjs-type).

Model

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

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.

One another important property (not used because <input/> doesn't need it) might be components, which defines default internal components

defaults: {
+  tagName: 'div',
+  attributes: { title: 'Hello' },
+  // Can be a string
+  components: `
+    <h1>Header test</h1>
+    <p>Paragraph test</p>
+  `,
+  // A component definition
+  components: {
+    tagName: 'h1',
+    components: 'Header test',
   },
-  // The second argument of .extend are static methods and we'll put inside our
-  // isComponent() method. As you're putting a new Component type on top of the stack,
-  // not declaring isComponent() might probably break stuff, especially if you extend
-  // the default one.
-  {
-    isComponent: function(el) {
-      if(el.tagName == 'INPUT'){
-        return {type: 'input'};
-      }
+  // Array of strings/component definitions
+  components: [
+    {
+      tagName: 'h1',
+      components: 'Header test',
     },
-  }),
+    '<p>Paragraph test</p>',
+  ],
+  // Or a function, which get as an argument the current
+  // model and expects as the return one of the possible
+  // values described above
+  components: model => {
+    return `<h1>Header test: ${model.get('type')}</h1>`;
+  },
+}
+

Read and update the model

You can read and update the model properties wherever you have the reference to it. Here some references to the most useful API

// let's use the selected component
+const modelComponent = editor.getSelected();
+
+// Get all the model properties
+const props = modelComponent.props();
+
+// Get a single property
+const tagName = modelComponent.get('tagName');
+
+// Update a single property
+modelComponent.set('tagName', '...');
 
-  // Define the View
-  view: defaultType.view,
+// Update multiple properties
+modelComponent.set({
+  tagName: '...',
+  // ...
 });
-

The code above is pretty much self-explanatory and as you see a lot of work is basically done on top of the Model properties. -The View is just extending the default one, so to cover also this part let's add some random behavior.

comps.addType('input', {
-  model: {...},
-  view: defaultType.view.extend({
-    // Bind events
-    events: {
-      // If you want to bind the event to children elements
-      // 'click .someChildrenClass': 'methodName',
-      click: 'handleClick',
-      dblclick: function(){
-        alert('Hi!');
-      }
+
+
+// Some helpers
+
+// Get all attributes
+const attrs = modelComponent.getAttributes();
+
+// Add attributes
+modelComponent.addAttributes({ title: 'Test' });
+
+// Replace all attributes
+modelComponent.setAttributes({ title: 'Test' });
+
+// Get the collection of all inner components
+modelComponent.components().forEach(
+  inner => console.log(inner.props())
+);
+
+// Update the inner content with an HTML string/Component Definitions
+const addedComponents = modelComponent.components(`<div>...</div>`);
+
+// Find components by query string
+modelComponent.find(`.query-string[example=value]`).forEach(
+  inner => console.log(inner.props())
+);
+

You'll notice that, on any change, the component in the canvas and its export code are changing accordingly

TIP

To know all the available methods/properties check the Component API

Listen to property changes

If you need to accomplish some kind of action on some property change you can set up listeners in the init method

editor.DomComponents.addType('my-input-type', {
+  // ...
+  model: {
+    defaults: {
+      // ...
+      someprop: 'initial value',
     },
 
-    // It doesn't make too much sense this method inside the component
-    // but it's ok as an example
-    randomHex: function() {
-      return '#' + Math.floor(Math.random()*16777216).toString(16);
+    init() {
+      this.on('change:someprop', this.handlePropChange);
+      // Listen to any attribute change
+      this.on('change:attributes', this.handleAttrChange);
+      // Listen to title attribute change
+      this.on('change:attributes:title', this.handleTitleChange);
     },
 
-    handleClick: function(e) {
-      this.model.set('style', {color: this.randomHex()}); // <- Affects the final HTML code
-      this.el.style.backgroundColor = this.randomHex(); // <- Doesn't affect the final HTML code
-      // Tip: updating the model will reflect the changes to the view, so, in this case,
-      // if you put the model change after the DOM one this will override the backgroundColor
-      // change made before
+    handlePropChange() {
+      const { someprop } = this.props();
+      console.log('New value of someprop: ', someprop);
     },
 
-    // The render() should return 'this'
-    render: function () {
-      // Extend the original render method
-      defaultType.view.prototype.render.apply(this, arguments);
-      this.el.placeholder = 'Text here'; // <- Doesn't affect the final HTML code
-      return this;
+    handleAttrChange() {
+      console.log('Attributes updated: ', this.getAttributes());
     },
-  }),
-});
-

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.

Update Component type

Here an example of how easily you can update/override the component

var originalMap = comps.getType('map');
 
-comps.addType('map', {
-  model: originalMap.model.extend({
-    // Override how the component is rendered to HTML
-    toHTML: function() {
-      return '<div>My Custom Map</div>';
+    handleTitleChange() {
+      console.log('Attribute title updated: ', this.getAttributes().title);
     },
-  }, {
-    isComponent: function(el) {
-      // ... new logic for isComponent
-		},
-  }),
-  view: originalMap.view
+  }
 });
-

Improvement over addType 0.14.50+

Now, with the 0.14.50 release, defining new components or extending them is a bit easier (without breaking the old process)

  • If you don't specify the type to extend, the default one will be used. In that case, you just -use objects for model and view
  • The defaults property, in the model, will be merged automatically with defaults of the parent component
  • If you use an object in model you can specify isComponent outside or omit it. In this case, -the isComponent is not mandatory but without it means the parser won't be able to identify the component -if not explicitly declared (eg. <div data-gjs-type="new-component">...</div>)

Before

const defaultType = comps.getType('default');
+

You'll find other lifecycle methods, like init, below

Now let's go back to our input component integration and see another useful part for the component customization

View

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 simplest 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'd expect, so you need to change some behavior. +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 even 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). Obviously, all of these changes have 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

For all of these cases, you can use the view in your Component Type Definition. 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

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('new-component', {
-  model: defaultType.model.extend({
-    defaults: {
-      ...defaultType.model.prototype.defaults,
-      someprop: 'somevalue',
+    // Add easily component specific listeners with `events`
+    // Being component specific (eg. you can't attach here listeners to window)
+    // you don't need to care about removing them when the component is removed,
+    // they will be managed automatically by the editor
+    events: {
+      click: 'clickOnElement',
+      // You can also make use of event delegation
+      // and listen to events bubbled from some inner element
+      'dblclick .inner-el': 'innerElClick',
     },
-    ...
-  }, {
-    // Even if it returns false, declaring isComponent is mandatory
-    isComponent(el) {
-      return false;
+
+    innerElClick(ev) {
+      ev.stopPropagation();
+      // ...
+
+      // If you need you can access the model from any function in the view
+      this.model.components('Update inner components');
     },
-  }),
-  view: defaultType.view.extend({ ... });
+
+    // On init you can create listeners, like in the model, or start some other
+    // function at the beginning
+    init({ model }) {
+      // Do something in view on model property change
+      this.listenTo(model, 'change:prop', this.handlePropChange);
+
+      // If you attach listeners on outside objects remember to unbind
+      // them in `removed` function in order to avoid memory leaks
+      this.onDocClick = this.onDocClick.bind(this);
+      document.addEventListener('click', this.onDocClick)
+    },
+
+    // Callback triggered when the element is removed from the canvas
+    removed() {
+      document.removeEventListener('click', this.onDocClick)
+    },
+
+    // Do something with the content once the element is rendered.
+    // The DOM element is passed as `el` in the argument object,
+    // but you can access it from any function via `this.el`
+    onRender({ el }) {
+      const btn = document.createElement('button');
+      btn.value = '+';
+      // This is just an example, AVOID adding events on inner elements,
+      // use `events` for these cases
+      btn.addEventListener('click', () => {});
+      el.appendChild(btn);
+    },
+
+    // Example of async content
+    async onRender({ el, model }) {
+      const asyncContent = await fetchSomething({
+        someDataFromModel: model.get('someData'),
+      });
+      // Remember, these changes exist only inside the editor canvas
+      // None of the DOM change is stored in your template data,
+      // if you need to store something, update the model properties
+      el.appendChild(asyncContent);
+    }
+  },
 });
-

After

comps.addType('new-component', {
-  // We can even omit isComponent here, as `false` return will be the default behavior
-  isComponent: el => false,
+

Update Component Type

Updating component types is quite easy, let's see how:

const domc = editor.DomComponents;
+
+domc.addType('some-component', {
+  // You can update the isComponent logic or leave the one from `some-component`
+  // isComponent: (el) => false,
+
+  // Update the model, if you need
   model: {
+    // The `defaults` property is handled differently
+    // and will be merged with the old `defaults`
     defaults: {
-      someprop: 'somevalue',
+      tagName: '...', // Overrides the old one
+      someNewProp: 'Hello', // Add new property
     },
-    ...
+    init() {
+      // Ovverride `init` function in `some-component`
+    }
   },
-  view: { ... };
-});
-
  • If you need to extend some component, you can use extend and extendView property.
  • You can now omit view property if you don't need to change it

Before

const originalMap = comps.getType('map');
-
-comps.addType('map', {
-  model: originalMap.model.extend({
-    ...
-  }, {
-    isComponent(el) {
-      // ... usually, you'd reuse the same logic
-    },
-  }),
-  // Even if I do nothing in view, I have to specify it
-  view: originalMap.view
-});
-

After

The map type is already defined, so it will be used as a base for the model and view. -We can skip isComponent if the recognition logic is the same of the extended component.

comps.addType('map', {
-  model: { ... },
+
+  // Update the view, if you need
+  view: {},
 });
-

Extend the model and view with some other, already defined, components.

comps.addType('map', {
+

Extend Component Type

Sometimes you would need to create a new type by extending another one. Just use extend and extendView indicating the component to extend.

comps.addType('my-new-component', {
+  isComponent: el => {/* ... */},
   extend: 'other-defined-component',
-  model: { ... }, // Will extend 'other-defined-component'
-  view: { ... }, // Will extend 'other-defined-component'
-  // `isComponent` will be taken from `map`
+  model: { ... }, // Will extend the model from 'other-defined-component'
+  view: { ... }, // Will extend the view from 'other-defined-component'
 });
-
comps.addType('map', {
+
comps.addType('my-new-component', {
+  isComponent: el => {/* ... */},
   extend: 'other-defined-component',
-  model: { ... }, // Will extend 'other-defined-component'
+  model: { ... }, // Will extend the model from 'other-defined-component'
   extendView: 'other-defined-component-2',
-  view: { ... }, // Will extend 'other-defined-component-2'
-  // `isComponent` will be taken from `map`
+  view: { ... }, // Will extend the view from 'other-defined-component-2'
 });
-

Extend parent functions 0.14.60+

When you need to reuse functions, of the parent you're extending, you can avoid writing something like this in any function:

domc.getType('parent-type').model.prototype.init.apply(this, arguments);
-

by using extendFn and extendFnView arrays:

domc.addType('new-type', {
+

Extend parent functions

When you need to reuse functions, from the parent you're extending, you can avoid writing this:

domc.getType('parent-type').model.prototype.init.apply(this, arguments);
+

by using extendFn and extendFnView options:

domc.addType('new-type', {
   extend: 'parent-type',
-  extendFn: ['init'], // array of model functions to extend
+  extendFn: ['init'], // array of model functions to extend from `parent-type`
   model: {
     init() {
-      // do something;
+      // do something
     },
   }
 });
-

The same would be for the view by using extendFnView

Lifecycle Hooks

Each component triggers different lifecycle hooks, which allows you to add custom actions at their specific stages. +

The same would be for the view by using extendFnView

TIP

If you need you can also get all the current component types by using getTypes

editor.DomComponents.getTypes().forEach(compType => console.log(compType.id))
+

Lifecycle Hooks

Each component triggers different lifecycle hooks, which allows you to add custom actions at their specific stages. We can distinguish 2 different types of hooks: global and local. You define local hooks when you create/extend a component type (usually via some model/view method) and the reason is to react to an event of that particular component type. Instead, the global one, will be called indistinctly on any component (you listen to them via editor.on) and you can make @@ -277,39 +436,54 @@ editor.on.on(`component:update:testprop`, model => console.log('Global hook: component:update:testprop', model.get('type'))); editor.on(`component:remove`, model => console.log('Global hook: component:remove', model.get('type')));

Components & JS

If you want to know how to create Components with javascript attached (eg. counters, galleries, slideshows, etc.) check the dedicated page -Components & JS

Hints

<div id="gjs">
- ...
- <cutom-element></cutom-element>
- ...
-</div>
-
-<script>
- var editor = grapesjs.init({
-      container : '#gjs',
-      fromElement: true,
-  });
-
-  editor.DomComponents.addType('cutom-element-type', {...});
-</script>
-

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)

Solution 1: turn off autorender

<script>
- var editor = grapesjs.init({
-      autorender: 0,
-      container : '#gjs',
-      fromElement: true,
-  });
-
-  editor.DomComponents.addType('cutom-element-type', {...});
-
-  // after all new types
-  editor.render();
-</script>
-

Solution 2: put all the stuff inside a plugin (Creating plugins)

Last Updated: 8/13/2019, 4:52:24 PM
Last Updated: 9/17/2019, 8:29:17 AM
- + diff --git a/docs/modules/Plugins.html b/docs/modules/Plugins.html index ab3462d77..d7d904882 100644 --- a/docs/modules/Plugins.html +++ b/docs/modules/Plugins.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Storage.html b/docs/modules/Storage.html index 421c60c04..c73d39555 100644 --- a/docs/modules/Storage.html +++ b/docs/modules/Storage.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Style-manager.html b/docs/modules/Style-manager.html index 8f3bce195..e66b19e6f 100644 --- a/docs/modules/Style-manager.html +++ b/docs/modules/Style-manager.html @@ -8,9 +8,9 @@ - - - + + +
- + diff --git a/docs/modules/Traits.html b/docs/modules/Traits.html index 084285e90..bec670c75 100644 --- a/docs/modules/Traits.html +++ b/docs/modules/Traits.html @@ -8,9 +8,9 @@ - - - + + +
GitHub -

Trait Manager

In GrapesJS, Traits define different parameters and behaviors of a component. The user generally will see traits as the Settings of a component. A common use of traits is to customize element attributes (eg. placeholder for <input>) or you can also bind them to the properties of your components and react on their changes.

Trait Manager

In GrapesJS, Traits define different parameters and behaviors of a component. The user generally will see traits as the Settings of a component. A common use of traits is to customize element attributes (eg. placeholder for <input>) or you can also bind them to the properties of your components and react on their changes.

WARNING

This guide is referring to GrapesJS v0.15.3 or higher.

To get a better understanding of the content in this guide we recommend reading Components first

Add Traits to Components

Generally you define traits on the definition of your new custom components (or by extending another one). Let's see in this example how to make inputs more customizable by the editor.

All components, by default, contain two traits: id and title (at the moment of writing). So, if you select an input and open the Settings panel you will see this:

We can start by creating a new custom input component in this way:

editor.DomComponents.addType('input', {
     isComponent: el => el.tagName == 'INPUT',
     model: {
@@ -368,6 +368,6 @@ The component should allow to read and write data from the instance →
       

- +