First of all, thank you for considering contributing to GrapesJS!
We welcome any type of contribution, not only code. Like for example:
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, tutorials, etc.
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, etc.
- **Money**: We welcome financial contributions in full transparency on our [Open Collective].
## Setting up the repository
This is a Node.js project and you need to have Node.js installed on your machine. You can download it from [here](https://nodejs.org/). We test versions 14 and 16 of Node in the CI, so it's recommended to use one of these versions, or the latest of: 16.20.2
@ -45,7 +45,6 @@ Navigate to `http://localhost:8080/` to see the editor in action. The developmen
Working on your first Pull Request? You can learn how from this **free** series, [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code
Any code change should be submitted as a pull request. Before start working on something make always a search in opened issues and pull requests, this might help you to avoid wasting time.
@ -54,29 +53,23 @@ A pull request could be a bug fix, new feature and much more, but in all cases,
The title should be brief but comprehensive, the description contains a link to the opened issue and the proposed solution. The pull request should contain tests whenever possible. Keep in mind that the bigger is the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
## Styleguide
The code is auto formatted with [prettier](https://github.com/prettier/prettier) on any commit, therefore you can write in any style you prefer
## Expenses
Anyone can file an expense (code, marketing, etc.) via our [Open Collective]. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
Before submitting an expense contact core contributors via the current active chat room ([Discord](https://discord.gg/QAbgGXq)) and explain your intents
## Questions
If you have any questions, create an [issue](https://github.com/GrapesJS/grapesjs/issues) (protip: do a quick search first to see if someone else didn't ask the same question before!).
## Credits
Thank you to all the people who have already contributed to GrapesJS!
GrapesJS is a free and open source Web Builder Framework which helps building HTML templates, faster and easily, to be delivered in sites, newsletters or mobile apps. Mainly, GrapesJS was designed to be used inside a [CMS] to speed up the creation of dynamic templates. To better understand this concept check the image below
<br/>
@ -21,86 +19,70 @@ This demos show examples of what is possible to achieve:<br/>
* [@grapesjs/react](https://github.com/GrapesJS/react) - GrapesJS wrapper for React that allows you to build custom and declarative UI for your editor.
- [@grapesjs/react](https://github.com/GrapesJS/react) - GrapesJS wrapper for React that allows you to build custom and declarative UI for your editor.
### Extensions
* [grapesjs-plugin-export](https://github.com/GrapesJS/export) - Export GrapesJS templates in a zip archive
* [grapesjs-plugin-filestack](https://github.com/GrapesJS/filestack) - Add Filestack uploader in Asset Manager
* [grapesjs-plugin-ckeditor](https://github.com/GrapesJS/ckeditor) - Replaces the built-in RTE with CKEditor
* [grapesjs-style-gradient](https://github.com/GrapesJS/style-gradient) - Add `gradient` type input to the Style Manager
* [grapesjs-style-filter](https://github.com/GrapesJS/style-filter) - Add `filter` type input to the Style Manager
* [grapesjs-style-bg](https://github.com/GrapesJS/style-bg) - Full-stack background style property type, with the possibility to add images, colors, and gradients
* [grapesjs-blocks-flexbox](https://github.com/GrapesJS/blocks-flexbox) - Add the flexbox block
* [grapesjs-lory-slider](https://github.com/GrapesJS/components-lory) - Slider component by using [lory](https://github.com/meandmax/lory)
* [grapesjs-touch](https://github.com/GrapesJS/touch) - Enable touch support
* [grapesjs-indexeddb](https://github.com/GrapesJS/storage-indexeddb) - Storage wrapper for IndexedDB
* [grapesjs-firestore](https://github.com/GrapesJS/storage-firestore) - Storage wrapper for [Cloud Firestore](https://firebase.google.com/docs/firestore)
* [grapesjs-parser-postcss](https://github.com/GrapesJS/parser-postcss) - Custom CSS parser for GrapesJS by using [PostCSS](https://github.com/postcss/postcss)
* [grapesjs-typed](https://github.com/GrapesJS/components-typed) - Typed component made by wrapping Typed.js library
* [grapesjs-ui-suggest-classes](https://github.com/silexlabs/grapesjs-ui-suggest-classes) - Enable auto-complete of classes in the SelectorManager UI
* [grapesjs-fonts](https://github.com/silexlabs/grapesjs-fonts) - Custom Fonts plugin, adds a UI to manage fonts in websites
* [grapesjs-symbols](https://github.com/silexlabs/grapesjs-symbols) - Symbols plugin to reuse elements in a website and accross pages
* [grapesjs-click](https://github.com/bgrand-ch/grapesjs-click) - Grab and drop blocks and components with click (no more drag-and-drop)
* [grapesjs-float](https://github.com/bgrand-ch/grapesjs-float) - Anchor a floating element next to another element (selected component, ...)
- [grapesjs-plugin-export](https://github.com/GrapesJS/export) - Export GrapesJS templates in a zip archive
- [grapesjs-plugin-filestack](https://github.com/GrapesJS/filestack) - Add Filestack uploader in Asset Manager
- [grapesjs-plugin-ckeditor](https://github.com/GrapesJS/ckeditor) - Replaces the built-in RTE with CKEditor
- [grapesjs-style-gradient](https://github.com/GrapesJS/style-gradient) - Add `gradient` type input to the Style Manager
- [grapesjs-style-filter](https://github.com/GrapesJS/style-filter) - Add `filter` type input to the Style Manager
- [grapesjs-style-bg](https://github.com/GrapesJS/style-bg) - Full-stack background style property type, with the possibility to add images, colors, and gradients
- [grapesjs-blocks-flexbox](https://github.com/GrapesJS/blocks-flexbox) - Add the flexbox block
- [grapesjs-lory-slider](https://github.com/GrapesJS/components-lory) - Slider component by using [lory](https://github.com/meandmax/lory)
- [grapesjs-touch](https://github.com/GrapesJS/touch) - Enable touch support
- [grapesjs-indexeddb](https://github.com/GrapesJS/storage-indexeddb) - Storage wrapper for IndexedDB
- [grapesjs-firestore](https://github.com/GrapesJS/storage-firestore) - Storage wrapper for [Cloud Firestore](https://firebase.google.com/docs/firestore)
- [grapesjs-parser-postcss](https://github.com/GrapesJS/parser-postcss) - Custom CSS parser for GrapesJS by using [PostCSS](https://github.com/postcss/postcss)
- [grapesjs-typed](https://github.com/GrapesJS/components-typed) - Typed component made by wrapping Typed.js library
- [grapesjs-ui-suggest-classes](https://github.com/silexlabs/grapesjs-ui-suggest-classes) - Enable auto-complete of classes in the SelectorManager UI
- [grapesjs-fonts](https://github.com/silexlabs/grapesjs-fonts) - Custom Fonts plugin, adds a UI to manage fonts in websites
- [grapesjs-symbols](https://github.com/silexlabs/grapesjs-symbols) - Symbols plugin to reuse elements in a website and accross pages
- [grapesjs-click](https://github.com/bgrand-ch/grapesjs-click) - Grab and drop blocks and components with click (no more drag-and-drop)
- [grapesjs-float](https://github.com/bgrand-ch/grapesjs-float) - Anchor a floating element next to another element (selected component, ...)
- [grapesjs-mjml](https://github.com/GrapesJS/mjml) - Newsletter Builder with MJML components
Find out more about plugins here: [Creating plugins](https://grapesjs.com/docs/modules/Plugins.html)
## Support
If you like the project and you wish to see it grow, please consider supporting us with a donation of your choice or become a backer/sponsor via [Open Collective](https://opencollective.com/grapesjs)
@ -214,12 +175,10 @@ If you like the project and you wish to see it grow, please consider supporting
In just few lines, with the default configurations, you're already able to see something with which play around.
[[img/default-gjs.jpg]]
@ -28,7 +29,6 @@ Of course all those stuff (panels, buttons, commands, etc.) are set just as defa
[[img/canvas-panels.jpg]]
If you'd like to extend the already instantiated editor you have to check [API Reference]. Check also [how to create plugins](./Creating-plugins) using the same API.
In this guide we'll focus on how to initialize the editor with all custom UI from scratch.
@ -48,6 +48,7 @@ var editor = grapesjs.init({
});
...
```
In this example we set a panel with 'commands' as an id and after the render we'll see nothing more than an empty div added to our panels. The new panel is already styled as the id 'commands' is one of the default but you can use whatever you like and place it wherever you want with CSS. With refresh we might see something like shown in the image below, with the new panel on the left:
[[img/new-panel.png]]
@ -79,7 +80,6 @@ Yeah, the button is pretty nice and happy, but useless without any command assig
> Check [Panels API Reference] for more details about Panels and Buttons
Assigning commands is pretty easy, but before you should define one or use one of defaults ([Built-in commands](./Built-in-commands)). So in this case we gonna create a new one.
```js
@ -111,17 +111,15 @@ Assigning commands is pretty easy, but before you should define one or use one o
}
...
```
As you see we added a new command `helloWorld` and used its `id` as an identifier inside `button.command`. In addition to this we've also implemented two required methods, `run` and `stop`, to make button execute commands.
[[img/btn-clicked.png]]
> Check [Commands API Reference]
Check the [demo](http://grapesjs.com/demo.html) for more complete usage of panels, buttons and built-in commands.
## Components
Components are elements inside the canvas, which can be drawn by commands or injected directly via configurations. In simple terms components represent the structure of our HTML document. You can init the editor with passing components as an HTML string
@ -136,6 +134,7 @@ Components are elements inside the canvas, which can be drawn by commands or inj
We added 3 simple components with some basic style. If you refresh probably you'll see the same empty page but are actually there, you only need to highlight them.
For this purpose already exists a command, so add it to your panel in this way
@ -161,6 +160,7 @@ For this purpose already exists a command, so add it to your panel in this way
},
...
```
Worth noting the use of `context` option (try to click 'smile' command without it) and `active` to enable it after the render.
Now you should be able to see blocks inside canvas.
@ -170,7 +170,6 @@ You could add other commands to enable interactions with blocks. Check [Built-in
> Check [Components API Reference]
## Style Manager
Any HTML structure requires, at some point, a proper style, so to meet this need the Style Manager was added as a built-in feature in GrapesJS. Style manager is composed by sectors, which group inside different types of CSS properties. So you can add, for instance, a `Dimension` sector for `width` and `height`, and another one as `Typography` for `font-size` and `color`. So it's up to you decide how organize sectors.
@ -377,7 +376,6 @@ As you can see using `buildProps` actually will save you a lot of work. You coul
> Check [Style Manager API Reference]
## Store/load data
In this last part we're gonna see how to store and load template data inside GrapesJS. You may already noticed that even if you refresh the page after changes on canvas your data are not lost and this because GrapesJS comes with some built-in storage implementation.
@ -448,11 +446,10 @@ If you prefer you could also disable autosaving and do it by yourself using some
> Check [Storage Manager API Reference]
[API Reference]: <API-Reference>
[Panels API Reference]: <API-Panels>
[Commands API Reference]: <API-Commands>
[Components API Reference]: <API-Components>
[Style Manager API Reference]: <API-Style-Manager>
[Editor API Reference]: <API-Editor>
[Storage Manager API Reference]: <API-Storage-Manager>
[API Reference]: API-Reference
[Panels API Reference]: API-Panels
[Commands API Reference]: API-Commands
[Components API Reference]: API-Components
[Style Manager API Reference]: API-Style-Manager
[Editor API Reference]: API-Editor
[Storage Manager API Reference]: API-Storage-Manager
At first glance one might think this is just another page/HTML builder, but it's something more. GrapesJS is a multi-purpose, Web Builder Framework, which means it allows you to easily create a drag & drop enabled builder of "things". By "things" we mean anything with HTML-like structure, which entails much more than web pages. We use HTML-like structure basically everywhere: Newsletters (eg. [MJML](https://mjml.io/)), Native Mobile Applications (eg. [React Native](https://github.com/facebook/react-native)), Native Desktop Applications (eg. [Vuido](https://vuido.mimec.org)), PDFs (eg. [React PDF](https://github.com/diegomura/react-pdf)), etc. So, for everything you can imagine as a set of elements like `<tag some="attribute">... other nested elements ...</tag>` you can create easily a GrapesJS builder around it and then use it independently in your applications.
GrapesJS ships with features and tools that enable you to craft easy to use builders. Which allows your users to create complex HTML-like templates without any knowledge of coding.
At first glance one might think this is just another page/HTML builder, but it's something more. GrapesJS is a multi-purpose, Web Builder Framework, which means it allows you to easily create a drag & drop enabled builder of "things". By "things" we mean anything with HTML-like structure, which entails much more than web pages. We use HTML-like structure basically everywhere: Newsletters (eg. [MJML](https://mjml.io/)), Native Mobile Applications (eg. [React Native](https://github.com/facebook/react-native)), Native Desktop Applications (eg. [Vuido](https://vuido.mimec.org)), PDFs (eg. [React PDF](https://github.com/diegomura/react-pdf)), etc. So, for everything you can imagine as a set of elements like `<tag some="attribute">... other nested elements ...</tag>` you can create easily a GrapesJS builder around it and then use it independently in your applications.
GrapesJS ships with features and tools that enable you to craft easy to use builders. Which allows your users to create complex HTML-like templates without any knowledge of coding.
## Why GrapesJS?
GrapesJS was designed primarily for use inside Content Management Systems to speed up the creation of dynamic templates and replace common WYSIWYG editors, which are good for content editing, but inappropriate for creating HTML structures. Instead of creating an application we decided to create an extensible framework that could be used by anyone for any purpose.
GrapesJS was designed primarily for use inside Content Management Systems to speed up the creation of dynamic templates and replace common WYSIWYG editors, which are good for content editing, but inappropriate for creating HTML structures. Instead of creating an application we decided to create an extensible framework that could be used by anyone for any purpose.
## Quick Start
To showcase the power of GrapesJS we have created some presets.
- [grapesjs-mjml](https://github.com/GrapesJS/mjml) - [Newsletter Builder with MJML](https://grapesjs.com/demo-mjml.html)
You can actually use them as a starting point for your editors, so, just follow the instructions on their repositories to get a quick start for your builder.
With just the canvas you're already able to move, copy and delete components from the structure. For now, we see the example template taken from the container. Next let's look at how to create and drag custom blocks into our canvas.
## Add Blocks
The block in GrapesJS is just a reusable piece of HTML that you can drop in the canvas. A block can be an image, a button, or an entire section with videos, forms and iframes. Let's start by creating another container and append a few basic blocks inside of it. Later we can use this technique to build more complex structures.
```html{4}
@ -55,6 +56,7 @@ The block in GrapesJS is just a reusable piece of HTML that you can drop in the
</div>
<divid="blocks"></div>
```
```js
const editor = grapesjs.init({
// ...
@ -64,16 +66,18 @@ const editor = grapesjs.init({
{
id: 'section', // id is mandatory
label: '<b>Section</b>', // You can use HTML/SVG inside labels
attributes: { class:'gjs-block-section' },
attributes: { class:'gjs-block-section' },
content: `<section>
<h1>This is a simple title</h1>
<div>This is just a Lorem text: Lorem ipsum dolor sit amet</div>
</section>`,
}, {
},
{
id: 'text',
label: 'Text',
content: '<divdata-gjs-type="text">Insert your text here</div>',
}, {
},
{
id: 'image',
label: 'Image',
// Select the component once it's dropped
@ -84,11 +88,12 @@ const editor = grapesjs.init({
// This triggers `active` event on dropped components and the `image`
If you want to learn more about blocks we suggest reading its dedicated article: [Block Manager Module](modules/Blocks.html).
:::
## Define Components
Technically, once you drop your HTML block inside the canvas each element of the content is transformed into a GrapesJS Component. A GrapesJS Component is an object containing information about how the element is rendered in the canvas (managed in the View) and how it might look in its final code (created by the properties in the Model). Generally, all Model properties are reflected in the View. Therefore, if you add a new attribute to the model, it will be available in the export code (which we will learn more about later), and the element you see in the canvas will be updated with new attributes.
This isn't totally out of the ordinary, but the unique thing about Components is that you can create a totally decoupled View. This means you can show the user whatever you desire regardless of what is in the Model. For example, by dragging a placeholder text you can fetch and show instead a dynamic content. If you want to learn more about Custom Components, you should check out [Component Manager Module](modules/Components.html).
GrapesJS comes with a few [built-in Components](modules/Components.html#built-in-component-types) that enable different features once rendered in the canvas. For example, by double clicking on an image component you will see the default [Asset Manager](modules/Assets.html), which you can customize or integrate your own. By double clicking on the text component you're able to edit it via the built-in Rich Text Editor, which is also customizable and [replaceable](guides/Replace-Rich-Text-Editor.html).
As we have seen before you can create Blocks directly as Components:
Now that we have a canvas and custom blocks let's see how to create a new custom panel with some buttons inside (using [Panels API](api/panels.html)) which trigger commands (the core one or custom).
```html{1,2,3}
@ -197,25 +209,29 @@ editor.Panels.addPanel({
className: 'btn-toggle-borders',
label: '<u>B</u>',
command: 'sw-visibility', // Built-in command
}, {
},
{
id: 'export',
className: 'btn-open-export',
label: 'Exp',
command: 'export-template',
context: 'export-template', // For grouping context of buttons from the same panel
@ -228,7 +244,7 @@ We have defined where to render the panel with `el: '#basic-panel'` and then for
Try to use [Commands](api/commands.html) when possible, they allow you to track actions globally. Commands also execute callbacks before and after their execution (you can even interrupt them).
Check out the [Panels API](api/panels.html) to see all the available methods.
:::
## Layers
Another utility tool you might find useful when working with web elements is the layer manager. It's a tree overview of the structure nodes and enables you to manage it easier. To enable it you just have to specify where you want to render it.
```html{4,5,6,7,8,9,10,11}
@ -260,40 +276,45 @@ Another utility tool you might find useful when working with web elements is the
// We define a default panel as a sidebar to contain layers
panels: {
defaults: [{
id: 'layers',
el: '.panel__right',
// Make the panel resizable
resizable: {
maxDim: 350,
minDim: 200,
tc: 0, // Top handler
cl: 1, // Left handler
cr: 0, // Right handler
bc: 0, // Bottom handler
// Being a flex child we need to change `flex-basis` property
// instead of the `width` (default)
keyWidth: 'flex-basis',
defaults: [
{
id: 'layers',
el: '.panel__right',
// Make the panel resizable
resizable: {
maxDim: 350,
minDim: 200,
tc: 0, // Top handler
cl: 1, // Left handler
cr: 0, // Right handler
bc: 0, // Bottom handler
// Being a flex child we need to change `flex-basis` property
// instead of the `width` (default)
keyWidth: 'flex-basis',
},
},
}]
}
],
},
});
```
<Demo>
<DemoLayers/>
</Demo>
## Style Manager
Once you have defined the structure of the template the next step is the ability to style it. To meet this need GrapesJS includes the Style Manager module which is composed by CSS style properties and sectors. To make it more clear, let's see how to define a basic set.
Let's start by adding one more panel inside the `panel__right` and another one in `panel__top` which will contain a Layer/Style Manager switcher:
@ -310,11 +331,13 @@ Let's start by adding one more panel inside the `panel__right` and another one i
@ -438,6 +475,7 @@ Each component can also indicate what to style and what not.
-->
## Traits
Most of the time you would style your components and place them somewhere in the structure, but sometimes your components might need custom attributes or even custom behaviors and for this need you can make use of traits. Traits are commonly used to update HTML element attributes (eg. `placeholder` for inputs or `alt` for images), but you can also define your own custom traits. Access the selected Component model and do whatever you want. For this guide, we are going to show you how to render available traits, for more details on how to extend them we suggest you read the [Trait Manager Module page](modules/Traits.html).
Let's create a new container for traits. Tell the editor where to render it and update the sidebar switcher:
Now if you switch to the Trait panel and select one of the inner components you should see its default traits.
## Responsive templates
GrapesJS implements a module which allows you to work with responsive templates easily. Let's see how to define different devices and a button for device switching:
```html{3}
@ -511,23 +551,28 @@ GrapesJS implements a module which allows you to work with responsive templates
</div>
...
```
```css
.panel__devices {
position: initial;
}
```
```js
const editor = grapesjs.init({
// ...
deviceManager: {
devices: [{
devices: [
{
name: 'Desktop',
width: '', // default size
}, {
},
{
name: 'Mobile',
width: '320px', // this value will be used on canvas width
widthMedia: '480px', // this value will be used in CSS @media
mediaCondition: 'min-width', // default is `max-width`
deviceManager: {
devices: [{
devices: [
{
name: 'Mobile',
width: '320',
widthMedia: '',
}, {
},
{
name: 'Desktop',
width: '',
widthMedia:'1024',
}]
widthMedia: '1024',
},
],
},
// ...
});
@ -601,24 +652,26 @@ Check out the [Device Manager API](api/device_manager.html) to see all the avail
:::
## Store & load data
Once you have finished with defining your builder interface the next step would be to setup the storing and loading process.
GrapesJS implements 2 simple type of storages inside its Storage Manager: The local (by using `localStorage`, active by default) and the remote one. Those are enough to cover most of the cases, but it's also possible to add new implementations ([grapesjs-indexeddb](https://github.com/GrapesJS/storage-indexeddb) is a good example).
Let's see how the default options work:
```js
grapesjs.init({
// ...
storageManager: {
type: 'local', // Type of the storage, available: 'local' | 'remote'
autosave: true, // Store data automatically
autoload: true, // Autoload stored data on init
stepsBeforeSave: 1, // If autosave enabled, indicates how many changes are necessary before store method is triggered
options: {
local: { // Options for the `local` type
key: 'gjsProject', // The key for the local storage
},
}
}
// ...
storageManager: {
type: 'local', // Type of the storage, available: 'local' | 'remote'
autosave: true, // Store data automatically
autoload: true, // Autoload stored data on init
stepsBeforeSave: 1, // If autosave enabled, indicates how many changes are necessary before store method is triggered
options: {
local: {
// Options for the `local` type
key: 'gjsProject', // The key for the local storage
},
},
},
});
```
@ -626,21 +679,22 @@ Let's take a look at the configuration required to setup the remote storage:
```js
grapesjs.init({
// ...
storageManager: {
type: 'remote',
// ...
storageManager: {
type: 'remote',
// ...
stepsBeforeSave: 10,
options: {
remote: {
headers: {}, // Custom headers for the remote storage request
urlStore: 'https://your-server/endpoint/store', // Endpoint URL where to store data project
urlLoad: 'https://your-server/endpoint/load', // Endpoint URL where to load data project
}
stepsBeforeSave: 10,
options: {
remote: {
headers: {}, // Custom headers for the remote storage request
urlStore: 'https://your-server/endpoint/store', // Endpoint URL where to store data project
urlLoad: 'https://your-server/endpoint/load', // Endpoint URL where to load data project
},
}
},
},
});
```
As you might noticed, we've left some default options unchanged, increased changes necessary for autosave triggering and passed remote endpoints.
If you prefer you could also disable the autosaving and use a custom command to trigger the store:
@ -669,6 +723,7 @@ If you prefer you could also disable the autosaving and use a custom command to
To get a better overview of the Storage Manager and how you should store/load the template, or how to define new storages you should read the [Storage Manager Module](modules/Storage.html) page.
## Theming
One last step that might actually improve a lot your editor personality is how it looks visually. To achieve an easy theming we have adapted an atomic design for this purpose. So for example to customize the main palette of colors all you have to do is to place your custom CSS rules after the GrapesJS styles.
To complete our builder let's customize its color palette and to make it more visually "readable" we can replace all button labels with SVG icons:
@ -706,6 +761,7 @@ To complete our builder let's customize its color palette and to make it more vi
There is also a bunch of [CSS custom properties (variables)](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) that you can use to customize the styles of the editor.
For example, you could achieve the same result as above by doing this:
```css
:root {
--gjs-primary-color: #78366a;
@ -715,10 +771,8 @@ For example, you could achieve the same result as above by doing this:
If you just use GrapesJS for building templates from scratch, so you start from an empty canvas and for editing you strictly rely on the generated JSON (final HTML/CSS only for end-users) then, probably, you might skip this guide. On the other hand, if you import templates from already defined HTML/CSS or let the user embed custom codes (eg. using the [grapesjs-custom-code](https://github.com/GrapesJS/components-custom-code) plugin), then you have to know that you might face strange behaviors.
If you just use GrapesJS for building templates from scratch, so you start from an empty canvas and for editing you strictly rely on the generated JSON (final HTML/CSS only for end-users) then, probably, you might skip this guide. On the other hand, if you import templates from already defined HTML/CSS or let the user embed custom codes (eg. using the [grapesjs-custom-code](https://github.com/GrapesJS/components-custom-code) plugin), then you have to know that you might face strange behaviors.
::: warning
This guide requires GrapesJS v0.14.33 or higher
@ -11,7 +12,6 @@ This guide requires GrapesJS v0.14.33 or higher
[[toc]]
## Import HTML/CSS
Importing already defined HTML/CSS is a really good feature as it lets you start editing immediately any kind of template and obviously, GrapesJS itself promotes this kind of approach
@ -19,13 +19,17 @@ Importing already defined HTML/CSS is a really good feature as it lets you start
```html
<divid="gjs">
<divclass="txt-red">Hello world!</div>
<style>.txt-red{color:red}</style>
<style>
.txt-red {
color: red;
}
</style>
</div>
<scripttype="text/javascript">
const editor = grapesjs.init({
container: '#gjs',
fromElement: true,
container: '#gjs',
fromElement: true,
});
</script>
```
@ -80,10 +84,10 @@ So, for our case we just take in account a simple rule, we'll parse it and print
As you might have noticed the default Rich Text Editor (RTE) is really tiny and so doesn't seem like a complete solution as a text editor. Instead of showing how to add new commands inside the default one we'll show how to completely replace it with another one.
@ -13,7 +14,6 @@ This guide is referring to GrapesJS v0.21.2 or higher
[[toc]]
## Interface
### Enable
@ -71,8 +71,6 @@ const editor = grapesjs.init({
});
```
### Disable
Once we know how to enable the RTE let's implement the method which disable it, so let's create the `disable()` function.
@ -90,8 +88,6 @@ editor.setCustomRte({
});
```
### Content
Each third-party library could handle the state of the content differently and what is actually rendered as a DOM in the preview might not rapresent the final HTML output. So, by default, GrapesJS takes the `innerHTML` as the final output directly from the DOM element but is highly recommended to specify the method responsable to return the final state as HTML string (each third-party library might handle it differently).
@ -106,8 +102,6 @@ editor.setCustomRte({
});
```
### Focus
The `focus()` method is just a helper used inside `enable()` and not required by the interface
@ -120,7 +114,7 @@ const focus = (el, rte) => {
}
el.contentEditable = true;
rte?.focus();
}
};
editor.setCustomRte({
// ...
@ -132,11 +126,9 @@ editor.setCustomRte({
});
```
## Toolbar position
Sometimes the default top-left position of the toolbar is not always what you need. For example, when you scroll the canvas and the toolbar reaches the top, you'd like to move it down. For this purpose, you can add a listener which applies your logic in this way:
Sometimes the default top-left position of the toolbar is not always what you need. For example, when you scroll the canvas and the toolbar reaches the top, you'd like to move it down. For this purpose, you can add a listener which applies your logic in this way:
One thing you have to keep in mind when using a custom RTE is that all the content and its behavior are handled by the library itself, the GrapesJS's component will just store the content as it is.
For example, when you create a link using the built-in RTE then you'll be able to select it and edit its `href` via Component Settings. With a custom RTE, it will be its own task to show the proper modal for the link editing.
Obviously, each third-party library has its own APIs and can present some limitations and drawbacks, so, a minimal knowledge of the library is a plus.
### Enable content parser
As an experimental feature, now it's possible to tell the editor to parse the returned HTML content from the custom RTE and store the result as components and not as a simple HTML string. This allows GrapesJS to handle the custom RTE more closely to the native implementation and enable features like [textable components](https://github.com/GrapesJS/grapesjs/issues/2771#issuecomment-1040486056).
@ -165,8 +154,6 @@ editor.setCustomRte({
});
```
## Plugins
For the CKEditor, you can find a complete plugin here [grapesjs-plugin-ckeditor](https://github.com/GrapesJS/ckeditor).
This feature is released as a beta from GrapesJS v0.21.11
To get a better understanding of the content in this guide we recommend reading [Components] first
:::
@ -14,7 +14,6 @@ Symbols are a special type of [Component] that allows you to easily reuse common
[[toc]]
## Concept
A Symbol created from a component retains the same shape and uses the same [Components API], but it includes a reference to other related Symbols. When you create a new Symbol from a Component, it creates a Main Symbol, and the original component becomes an Instance Symbol.
@ -29,13 +28,10 @@ Below is a simple representation of the connection between Main and Instance Sym
This feature operates at a low level, meaning there is no built-in UI for creating and managing symbols. Developers need to implement their own UI to interact with this feature. Below you'll find an example of implementation.
:::
## Programmatic usage
Let's see how to work with and manage Symbols in your project.
### Create symbol
Create a new Symbol from any component in your project:
Once you have Symbols in your project, you might need to know when a Component is a Symbol and get details about it. Use the `getSymbolInfo` method for this:
When you update a Symbol's properties, changes are propagated to all related Symbols. To avoid propagating specific properties, you can specify at the component level which properties to skip:
Once you have Symbol instances you might need to disconnect one to create a new custom shape with other components inside, in that case you can use `detachSymbol`.
Check the full list of available options here: [Asset Manager Config](https://github.com/GrapesJS/grapesjs/blob/master/src/asset_manager/config/config.ts)
## Initialization
The Asset Manager is ready to work by default, so pass few URLs to see them loaded
@ -68,15 +63,12 @@ const editor = grapesjs.init({
});
```
If you want a complete list of available properties check out the source [AssetImage Model](https://github.com/GrapesJS/grapesjs/blob/dev/src/asset_manager/model/AssetImage.ts)
The built-in Asset Manager modal is implemented and is showing up when requested. By default, you can make it appear by dragging Image Components in canvas, double clicking on images and all other stuff related to images (eg. CSS styling)
Now you should be able to change the image of the component.
-->
## Uploading assets
The default Asset Manager includes also an easy to use, drag-and-drop uploader with a few UI helpers. The default uploader is already visible when you open the Asset Manager.
<img:src="$withBase('/assets-uploader.png')">
You can click on the uploader to select your files or just drag them directly from your computer to trigger the uploader. Obviously, before it will work you have to setup your server to receive your assets and specify the upload endpoint in your configuration
```js
const editor = grapesjs.init({
...
@ -128,7 +113,6 @@ const editor = grapesjs.init({
});
```
### Listeners
If you want to execute an action before/after the uploading process (eg. loading animation) or even on response, you can make use of these listeners
When the uploading is over, by default (via config parameter `autoAdd: 1`), the editor expects to receive a JSON of uploaded assets in a `data` key as a response and tries to add them to the main collection. The JSON might look like this:
@ -172,11 +155,10 @@ When the uploading is over, by default (via config parameter `autoAdd: 1`), the
If you need to manage your assets programmatically you have to use its [APIs][API-Asset-Manager]
@ -212,8 +190,9 @@ const am = editor.AssetManager;
```
First of all, it's worth noting that Asset Manager keeps 2 collections of assets:
* **global** - which is just the one with all available assets, you can get it with `am.getAll()`
* **visible** - this is the collection which is currently rendered by the Asset Manager, you get it with `am.getAllVisible()`
- **global** - which is just the one with all available assets, you can get it with `am.getAll()`
- **visible** - this is the collection which is currently rendered by the Asset Manager, you get it with `am.getAllVisible()`
This allows you to decide which assets to show and when. Let's say we'd like to have a category switcher, first of all you gonna add to the **global** collection all your assets (which you may already defined at init by `config.assetManager.assets = [...]`)
am.getAllVisible().length; // but only 2 are shown
```
You can also mix arrays of assets
@ -262,6 +241,7 @@ You can also mix arrays of assets
```js
am.render([...assets1, ...assets2, ...assets3]);
```
<!--
If you want to customize the asset manager container you can get its `HTMLElement`
@ -295,25 +275,21 @@ You can open the Asset Manager with your own select logic.
```js
am.open({
types: ['image'], // This is the default option
// Without select, nothing will happen on asset selection
select(asset, complete) {
const selected = editor.getSelected();
if (selected && selected.is('image')) {
selected.addAttributes({ src: asset.getSrc() });
// The default AssetManager UI will trigger `select(asset, false)`
// on asset click and `select(asset, true)` on double-click
complete && am.close();
}
}
types: ['image'], // This is the default option
// Without select, nothing will happen on asset selection
select(asset, complete) {
const selected = editor.getSelected();
if (selected && selected.is('image')) {
selected.addAttributes({ src: asset.getSrc() });
// The default AssetManager UI will trigger `select(asset, false)`
// on asset click and `select(asset, true)` on double-click
complete && am.close();
}
},
});
```
## Customization
The default Asset Manager UI is great for simple things, but except the possibility to tweak some CSS style, adding more complex things like a search input, filters, etc. requires a replace of the default UI.
@ -322,24 +298,23 @@ All you have to do is to indicate the editor your intent to use a custom UI and
```js
const editor = grapesjs.init({
// ...
assetManager: {
// ...
assetManager: {
// ...
custom: true,
},
custom: true,
},
});
editor.on('asset:custom', props => {
// The `props` will contain all the information you need in order to update your UI.
// props.open (boolean) - Indicates if the Asset Manager is open
// props.assets (Array<Asset>) - Array of all assets
@ -18,7 +18,6 @@ This guide is referring to GrapesJS v0.17.27 or higher
[[toc]]
## Configuration
To change the default configurations you have to pass the `blockManager` property with the main configuration object.
@ -35,7 +34,6 @@ const editor = grapesjs.init({
Check the full list of available options here: [Block Manager Config](https://github.com/GrapesJS/grapesjs/blob/master/src/block_manager/config/config.ts)
## Initialization
By default, Block Manager UI is considered a hidden component. Currently, the GrapesJS core, renders default panels and buttons that allow you to show them, but in long term, this is something that might will change. Here below you can see how to init the editor without default panels and immediately rendered Block Manager UI.
@ -65,20 +63,18 @@ const editor = grapesjs.init({
// We want to activate it once dropped in the canvas.
activate: true,
// select: true, // Default with `activate: true`
}
},
],
}
},
});
```
## Block content types
The key of connecting blocks to components is the `block.content` property and what we passed in the example above is the [Component Definition]. This is the component-oriented way to create blocks and this is how we highly recommend the creation of your blocks.
### Component-oriented
The `content` can accept different formats, like an HTML string (which will be parsed and converted to components), but the component-oriented approach is the most precise as you can keep the control of your each dropped block in the canvas. Another advice is to keep your blocks' [Component Definition] as light as possible, if you're defining a lot of redundant properties, probably it makes sense to create another dedicated component, this might reduce the size of your project JSON file. Here an example:
Here we're reusing the same component multiple times with the same set of properties (just an example, makes more sense with composed content of components), this can be reduced to something like this.
Using HTML strings as `content` is not wrong, in some cases you don't need the finest control over components and want to leave the user full freedom on template composition (eg. static site builder editor with HTML copy-pasted from a framework like [Tailwind Components](https://tailwindcomponents.com/))
```js
// Your block
{
@ -130,9 +129,10 @@ Using HTML strings as `content` is not wrong, in some cases you don't need the f
<divclass="el-Y el-A">Element A</div>
<divclass="el-Y el-B">Element B</div>
<divclass="el-Y el-C">Element C</div>
</div>`
</div>`;
}
```
In such a case, all rendered elements will be converted to the best suited default component (eg. `.el-Y` elements will be treated like `text` components). The user will be able to style and drag them with no particular restrictions.
Thanks to Components' [isComponet](Components.html#iscomponent) feature (executed post parsing), you're still able to bind your rendered elements to components and enforce an extra logic. Here an example how you would enforce all `.el-Y` elements to be placed only inside `.el-X` one, without touching any part of the original HTML used in the `content`.
@ -141,15 +141,16 @@ Thanks to Components' [isComponet](Components.html#iscomponent) feature (execute
// Your component
editor.Components.addType('cmp-Y', {
// Detect '.el-Y' elements
isComponent: el => el.classList?.contains('el-Y'),
Here we showed all the possibilities you have with HTML strings, but we strongly advise against the abuse of the `Option 2` and to stick to a more component-oriented approach.
Without a proper component type, not only your HTML will be harder to read, but all those defined properties will be "hard-coded" to a generic component of those elements. So, if one day you decide to "upgrade" the logic of the component (eg. `draggable: '.el-X'` -> `draggable: '.el-X, .el-Z'`), you won't be able.
### Mixed
It's also possible to mix components with HTML strings by passing an array.
```js
{
// ...
@ -208,7 +210,6 @@ It's also possible to mix components with HTML strings by passing an array.
}
```
## Important caveats
::: danger Read carefully
@ -218,6 +219,7 @@ It's also possible to mix components with HTML strings by passing an array.
### Avoid non serializable properties
Don't put non serializable properties, like functions, in your blocks, keep them only in your components.
```js
// Your block
{
@ -227,10 +229,13 @@ Don't put non serializable properties, like functions, in your blocks, keep them
},
}
```
This will work, but if you try to save and reload a stored project, those will disappear.
### Avoid styles
Don't put styles in your blocks, keep them always in your components.
```js
// Your block
{
@ -251,17 +256,15 @@ Don't put styles in your blocks, keep them always in your components.
],
}
```
<!-- Styles imported via component definition, by using the `styles` property, are connected to that specific component type. This allows the editor to remove automatically those styles if all related components are deleted. -->
With the component-oriented approach, you put yourself in a risk of conflicting styles and having a lot of useless redundant styles definitions in your project JSON.
With the HTML string, if you remove all related elements, the editor is not able to clean those styles from the project JSON, as there is no safe way to connect them.
## Programmatic usage
If you need to manage your blocks programmatically you can use its [APIs][Blocks API].
::: warning
@ -269,6 +272,7 @@ All Blocks API methods update mainly your Block Manager UI, it has nothing to do
:::
Below an example of commonly used methods.
```js
// Get the BlockManager module first
const bm = editor.Blocks; // `Blocks` is an alias of `BlockManager`
To know more about the available block properties, check the [Block API Reference][Block].
## Customization
The default Block Manager UI is great for simple things, but except the possibility to tweak some CSS style, adding more complex elements requires a replace of the default UI.
All you have to do is to indicate the editor your intent to use a custom UI and then subscribe to the `block:custom` event that will give you all the information on any requested change.
```js
const editor = grapesjs.init({
// ...
blockManager: {
// ...
blockManager: {
// ...
custom: true,
},
custom: true,
},
});
editor.on('block:custom', props => {
// The `props` will contain all the information you need in order to update your UI.
// props.blocks (Array<Block>) - Array of all blocks
// props.dragStart (Function<Block>) - A callback to trigger the start of block dragging.
// props.dragStop (Function<Block>) - A callback to trigger the stop of block dragging.
// props.container (HTMLElement) - The default element where you can append your UI
// Here you would put the logic to render/update your UI.
editor.on('block:custom', (props) => {
// The `props` will contain all the information you need in order to update your UI.
// props.blocks (Array<Block>) - Array of all blocks
// props.dragStart (Function<Block>) - A callback to trigger the start of block dragging.
// props.dragStop (Function<Block>) - A callback to trigger the stop of block dragging.
// props.container (HTMLElement) - The default element where you can append your UI
// Here you would put the logic to render/update your UI.
});
```
@ -329,15 +329,10 @@ Here an example of using custom Block Manager with a Vue component.
From the demo above you can also see how we decided to hide our custom Block Manager and append it to the default container, but that is up to your preferences.
## Events
For a complete list of available events, you can check it [here](/api/block_manager.html#available-events).
@ -14,7 +14,6 @@ This guide is referring to GrapesJS v0.21.5 or higher
[[toc]]
## Configuration
To change the default configurations you have to pass the `canvas` property with the main configuration object.
@ -30,7 +29,6 @@ const editor = grapesjs.init({
Check the full list of available options here: [Canvas Config](https://github.com/GrapesJS/grapesjs/blob/master/src/canvas/config/config.ts)
## Canvas Spots
Canvas spots are elements drawn on top of the canvas. They can be used to represent anything you might need but the most common use case of canvas spots is rendering information and managing components rendered in the canvas.
@ -47,9 +45,11 @@ The `select` type is responsable for showing selected components and rendering t
* The spots container, by default, relies on `pointer-events: none`, in order to prevent the spot from blocking the interaction with the components. This is why we have to re-enable the pointer event on the button in order to make it interactable.
- The spots container, by default, relies on `pointer-events: none`, in order to prevent the spot from blocking the interaction with the components. This is why we have to re-enable the pointer event on the button in order to make it interactable.
The second argument, `sender`, just indicates who requested the command, in our case will be always the `editor`
Until now there is nothing exciting except a common entry point for functions, but we'll see later its real advantages.
## Default commands
GrapesJS comes along with some default set of commands and you can get a list of all currently available commands via `editor.Commands.getAll()`. This will give you an object of all available commands, so, also those added later, like via plugins. You can recognize default commands by their namespace `core:*`, we also recommend to use namespaces in your own custom commands, but let's get a look more in detail here:
* [`core:canvas-clear`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/CanvasClear.ts) - Clear all the content from the canvas (HTML and CSS)
* [`core:component-delete`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentDelete.ts) - Delete a component
* [`core:component-enter`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentEnter.ts) - Select the first children component of the selected one
* [`core:component-exit`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentExit.ts) - Select the parent component of the current selected one
* [`core:component-next`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentNext.ts) - Select the next sibling component
* [`core:component-prev`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentPrev.ts) - Select the previous sibling component
* [`core:component-outline`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/SwitchVisibility.ts) - Enable outline border on components
* [`core:component-select`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/SelectComponent.ts) - Enable the process of selecting components in the canvas
* [`core:copy`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/CopyComponent.ts) - Copy the current selected component
* [`core:preview`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/Preview.ts) - Show the preview of the template in canvas
* [`core:fullscreen`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/Fullscreen.ts) - Set the editor fullscreen
* [`core:open-code`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ExportTemplate.ts) - Open a default panel with the template code
* [`core:open-layers`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenLayers.ts) - Open a default panel with layers
* [`core:open-styles`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenStyleManager.ts) - Open a default panel with the style manager
* [`core:open-traits`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenTraitManager.ts) - Open a default panel with the trait manager
* [`core:open-blocks`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenBlocks.ts) - Open a default panel with the blocks
* [`core:open-assets`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenAssets.ts) - Open a default panel with the assets
* `core:undo` - Call undo operation
* `core:redo` - Call redo operation
<!-- * `core:canvas-move` -->
<!-- * `core:component-drag` -->
<!-- * `core:component-style-clear` -->
<!-- tlb-clone tlb-delete tlb-move -->
- [`core:canvas-clear`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/CanvasClear.ts) - Clear all the content from the canvas (HTML and CSS)
- [`core:component-delete`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentDelete.ts) - Delete a component
- [`core:component-enter`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentEnter.ts) - Select the first children component of the selected one
- [`core:component-exit`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentExit.ts) - Select the parent component of the current selected one
- [`core:component-next`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentNext.ts) - Select the next sibling component
- [`core:component-prev`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ComponentPrev.ts) - Select the previous sibling component
- [`core:component-outline`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/SwitchVisibility.ts) - Enable outline border on components
- [`core:component-select`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/SelectComponent.ts) - Enable the process of selecting components in the canvas
- [`core:copy`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/CopyComponent.ts) - Copy the current selected component
- [`core:preview`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/Preview.ts) - Show the preview of the template in canvas
- [`core:fullscreen`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/Fullscreen.ts) - Set the editor fullscreen
- [`core:open-code`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/ExportTemplate.ts) - Open a default panel with the template code
- [`core:open-layers`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenLayers.ts) - Open a default panel with layers
- [`core:open-styles`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenStyleManager.ts) - Open a default panel with the style manager
- [`core:open-traits`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenTraitManager.ts) - Open a default panel with the trait manager
- [`core:open-blocks`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenBlocks.ts) - Open a default panel with the blocks
- [`core:open-assets`](https://github.com/GrapesJS/grapesjs/blob/dev/src/commands/view/OpenAssets.ts) - Open a default panel with the assets
- `core:undo` - Call undo operation
- `core:redo` - Call redo operation
<!-- * `core:canvas-move` -->
<!-- * `core:component-drag` -->
<!-- * `core:component-style-clear` -->
<!-- tlb-clone tlb-delete tlb-move -->
## Stateful commands
@ -203,17 +194,13 @@ If you run it, close the modal (eg. by clicking the 'x' on top) and then try to
In the example above, we make use of few helper methods from the Modal module (`onceClose`) and the command itself (`stopCommand`) but obviously, the logic might be different due to your requirements and specific UI.
## Extending
Another big advantage of commands is the possibility to easily extend or override them with another command.
Sometimes you might need to interrupt the execution of an existant command due to some condition. In that case, you have to use `command:run:before:{COMMAND-ID}` event and set to `true` the abort option
@ -305,7 +286,7 @@ Sometimes you might need to interrupt the execution of an existant command due t
The Commands module is quite simple but, at the same time, really powerful if used correctly. So, if you're creating a plugin for GrapesJS, use commands as much as possible, this will allow higher reusability and control over your logic.
Now if you drag the new block inside the canvas you'll see an alert and the message in console, as you might expect.
One thing worth noting is that `this` context is bound to the component's element, so, for example, if you need to change its property, you'd do `this.innerHTML = 'inner content'`.
@ -58,12 +58,12 @@ One thing you should take into account is how the script is bound to component o
<script>
var items = document.querySelectorAll('#c764');
for (var i = 0, len = items.length; i <len;i++){
(function(){
(function(){
// START component code
alert('Hi');
console.log('the element', this)
console.log('the element', this);
// END component code
}.bind(items[i]))();
}).bind(items[i])();
}
</script>
```
@ -76,12 +76,12 @@ As you see the editor attaches a unique ID to all components with scripts and re
<script>
var items = document.querySelectorAll('#c764, #c765');
for (var i = 0, len = items.length; i <len;i++){
(function(){
(function(){
// START component code
alert('Hi');
console.log('the element', this)
console.log('the element', this);
// END component code
}.bind(items[i]))();
}).bind(items[i])();
}
</script>
```
@ -99,7 +99,7 @@ That means **you can't use stuff outside of the function scope**. Take a look at
```js
const myVar = 'John';
const script = function() {
const script = function() {
alert('Hi ' + myVar);
console.log('the element', this);
};
@ -112,10 +112,10 @@ This won't work. You'll get an error of the undefined `myVar`. The final HTML sh
<script>
var items = document.querySelectorAll('#c764');
for (var i = 0, len = items.length; i <len;i++){
(function(){
(function(){
alert('Hi ' + myVar); // <-ERROR:undefinedmyVar
console.log('the element', this);
}.bind(items[i]))();
}).bind(items[i])();
}
</script>
```
@ -127,7 +127,7 @@ You can do it by using the `script-props` property on your component.
```js
// The `props` argument will contain only the properties you have declared in `script-props`
// Define which properties to pass (this will also reset your script on their changes)
'script-props': ['myprop1', 'myprop2'],
// ...
}
}
},
},
});
```
Now, if you try to change traits, you'll also see how the script will be triggered with the new updated properties.
Now, if you try to change traits, you'll also see how the script will be triggered with the new updated properties.
## Dependencies
@ -182,8 +180,8 @@ The component related approach is definitely the best one, as the dependencies w
All you have to do is to load your dependencies before executing your initialization script.
```js
const script = function(props) {
const initLib = function() {
const script = function(props) {
const initLib = function() {
const el = this;
const myLibOpts = {
prop1: props.myprop1,
@ -217,8 +215,7 @@ const editor = grapesjs.init({
}
});
```
Keep in mind that the editor won't render those dependencies in the exported HTML (eg. via `editor.getHtml()`), so it's up to you how to include them in the final page where the final HTML is rendered.
Keep in mind that the editor won't render those dependencies in the exported HTML (eg. via `editor.getHtml()`), so it's up to you how to include them in the final page where the final HTML is rendered.
If you need to append a component 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
@ -85,8 +83,6 @@ You might notice the result is similar to what is generally called a **Virtual D
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](#built-in-component-types)) 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 matters 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.
@ -98,8 +94,6 @@ If you're importing big string chunks of HTML code you might want to improve the
Read [here](#setup-jsx-syntax) 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.
@ -142,7 +140,7 @@ This will return a string containing the HTML of the component and all of its ch
The component implements also `toJSON` methods so you can get its JSON structure in this way
```js
JSON.stringify(component)
JSON.stringify(component);
```
::: tip
@ -151,8 +149,6 @@ 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).
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.
<!--
TODO
A more advanced use case of custom components is an implementation of a custom renderer inside of them
-->
## 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`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableCell.ts) - Component for handle `<td>` and `<th>` elements
* [`row`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableRow.ts) - Component for handle `<tr>` elements
* [`table`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTable.ts) - Component for handle `<table>` elements
* [`thead`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableHead.ts) - Component for handle `<thead>` elements
* [`tbody`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableBody.ts) - Component for handle `<tbody>` elements
* [`tfoot`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableFoot.ts) - Component for handle `<tfoot>` elements
* [`map`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentMap.ts) - Component for handle `<a>` elements
* [`link`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentLink.ts) - Component for handle `<a>` elements
* [`label`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentLabel.ts) - Component for handle properly `<label>` elements
* [`video`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentVideo.ts) - Component for videos
* [`image`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentImage.ts) - Component for images
* [`script`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentScript.ts) - Component for handle `<script>` elements
* [`svg`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentSvg.ts) - Component for handle SVG elements
* [`comment`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentComment.ts) - Component for comments (might be useful for email editors)
* [`textnode`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTextNode.ts) - Similar to the textnode in DOM definition, so a text element without a tag element.
* [`text`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentText.ts) - A simple text component that can be edited inline
* [`wrapper`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentWrapper.ts) - The canvas need to contain a root component, a wrapper, this component was made to identify it
* [`default`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/Component.ts) Default base component
- [`cell`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableCell.ts) - Component for handle `<td>` and `<th>` elements
- [`row`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableRow.ts) - Component for handle `<tr>` elements
- [`table`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTable.ts) - Component for handle `<table>` elements
- [`thead`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableHead.ts) - Component for handle `<thead>` elements
- [`tbody`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableBody.ts) - Component for handle `<tbody>` elements
- [`tfoot`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTableFoot.ts) - Component for handle `<tfoot>` elements
- [`map`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentMap.ts) - Component for handle `<a>` elements
- [`link`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentLink.ts) - Component for handle `<a>` elements
- [`label`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentLabel.ts) - Component for handle properly `<label>` elements
- [`video`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentVideo.ts) - Component for videos
- [`image`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentImage.ts) - Component for images
- [`script`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentScript.ts) - Component for handle `<script>` elements
- [`svg`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentSvg.ts) - Component for handle SVG elements
- [`comment`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentComment.ts) - Component for comments (might be useful for email editors)
- [`textnode`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentTextNode.ts) - Similar to the textnode in DOM definition, so a text element without a tag element.
- [`text`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentText.ts) - A simple text component that can be edited inline
- [`wrapper`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/ComponentWrapper.ts) - The canvas need to contain a root component, a wrapper, this component was made to identify it
- [`default`](https://github.com/GrapesJS/grapesjs/blob/dev/src/dom_components/model/Component.ts) Default base component
## Define Custom Component Type
@ -219,14 +205,14 @@ Now that we know how components work, we can start exploring the process of crea
<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 => {
const myNewComponentTypes = (editor) => {
editor.DomComponents.addType(/* API for component type definition */);
};
const editor = grapesjs.init({
container: '#gjs',
container: '#gjs',
// ...
plugins: [myNewComponentTypes],
plugins: [myNewComponentTypes],
});
```
@ -235,7 +221,7 @@ Let's say we want to make the editor understand and handle better `<input>` elem
```js
editor.DomComponents.addType('my-input-type', {
// Make the editor understand when to bind `my-input-type`
@ -265,8 +248,6 @@ With this code, the editor will be able to understand simple text `<input>`s, as
To understand better how Traits work you should read its [dedicated page](Traits.html) 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](#component-recognition-and-component-type-stack) section and we need it to make the editor understand `<input>` during the component recognition step.
@ -274,7 +255,7 @@ It receives only the `el` argument, which is the parsed HTMLElement node and exp
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.
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` 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.
console.log('Attribute title updated: ', this.getAttributes().title);
},
}
},
});
```
@ -494,20 +466,19 @@ 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
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.
- 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.
* 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
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.
* You need to customize the content or fill it with some data from the server
- 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
This approach allows the editor to group these styles ([CssRule] instances) and remove them accordingly in case all references of the same component are removed.
By default, when you select a component in the canvas and apply styles on it, changes will be applied on its existent classes. This will result on changing of all the components with those applied classes. If you need the style to be applied only on the specific selected component you have to select componentFirst strategy in this way.
```js
grapesjs.init({
...
@ -848,23 +809,18 @@ grapesjs.init({
},
})
```
:::
### External CSS
If you need to load external component-specific CSS, you have to rely on the `script` property. For more details please refer to [Components & JS](Components-js.html).
## 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](Components-js.html)
## Tips
### JSX syntax
@ -873,6 +829,7 @@ If you're importing big chunks of HTML string into the editor (eg. defined via B
By default, GrapesJS understands objects generated from React JSX preset, so, if you're working in the React app probably you're already using JSX and you don't need to do anything else, your environment is already configured to parse JSX in javascript files.
So, instead of writing this:
```js
// I'm adding a string, so the parsing and the component recognition steps will be executed
editor.addComponents(`<div>
@ -881,7 +838,9 @@ editor.addComponents(`<div>
</span>
</div>`);
```
or this
```js
// I'm passing the Component Definition, so heavy steps will be skipped but the code is less readable
Another cool feature you will get by using JSX is the ability to pass component types as element tags `<custom-component>` instead of `data-gjs-type="custom-component"`
#### Setup JSX syntax
For those who are not using React you have the following options:
* GrapesJS has an option, `config.domComponents.processor`, thats allows you to easily implement other JSX presets. This scenario is useful if you work with a framework different from React but that uses JSX (eg. Vue). In that case, the result object from JSX pragma function (React uses `React.createElement`) will be different (you can log the JSX to see the result object) and you have to transform that in GrapesJS [Component Definition] object. Below an example of usage
- GrapesJS has an option, `config.domComponents.processor`, thats allows you to easily implement other JSX presets. This scenario is useful if you work with a framework different from React but that uses JSX (eg. Vue). In that case, the result object from JSX pragma function (React uses `React.createElement`) will be different (you can log the JSX to see the result object) and you have to transform that in GrapesJS [Component Definition] object. Below an example of usage
```js
grapesjs.init({
@ -926,27 +890,24 @@ grapesjs.init({
})
```
* In case you need to support JSX from scratch (you don't use a framework which supports JSX) you have, at first, implement the parser which transforms JSX in your files in something JS-readable.
- In case you need to support JSX from scratch (you don't use a framework which supports JSX) you have, at first, implement the parser which transforms JSX in your files in something JS-readable.
For Babel users, it's just a matter of adding few plugins: `@babel/plugin-syntax-jsx` and `@babel-plugin-transform-react`. Then update your `.babelrc` file
```json
{
“plugins”: [
“@babel/plugin-syntax-jsx”,
“@babel/plugin-transform-react-jsx”
]
}
```
You can also customize the pragma function which executes the transformation `[“@babel/plugin-transform-react-jsx”, { “pragma”: “customCreateEl” }]`, by default `React.createElement` is used (you'll need a React instance available in the file to make it work).
A complete example of this approach can be found [here](https://codesandbox.io/s/x07xf)
```json
{
“plugins”: [
“@babel/plugin-syntax-jsx”,
“@babel/plugin-transform-react-jsx”
]
}
```
You can also customize the pragma function which executes the transformation `[“@babel/plugin-transform-react-jsx”, { “pragma”: “customCreateEl” }]`, by default `React.createElement` is used (you'll need a React instance available in the file to make it work).
A complete example of this approach can be found [here](https://codesandbox.io/s/x07xf)
@ -14,8 +14,6 @@ The module was added recently so we're open to receive support in [translating s
[[toc]]
## Configuration
By default, the editor includes only the English language, if you need other languages you have to import them manually.
@ -39,8 +37,6 @@ const editor = grapesjs.init({
Now the editor will be translated in Italian for those browsers which default language is Italian (by default `detectLocale` option is enabled)
## Update strings
If you need to change some default language strings you can easily update them by using [I18n API](/api/i18n.html).
@ -67,11 +63,12 @@ So now to update it you'll do this
```js
editor.I18n.addMessages({
en: { // indicate the locale to update
styleManager: {
empty: 'New empty state message',
}
}
en: {
// indicate the locale to update
styleManager: {
empty: 'New empty state message',
},
},
});
```
@ -114,17 +111,17 @@ you'd this
```js
editor.I18n.addMessages({
en: {
styleManager: {
properties: {
// The key is the property name (or id)
'margin-top': 'Top',
'margin-right': 'Right',
'margin-left': 'Left',
'margin-bottom': 'Bottom',
},
}
}
en: {
styleManager: {
properties: {
// The key is the property name (or id)
'margin-top': 'Top',
'margin-right': 'Right',
'margin-left': 'Left',
'margin-bottom': 'Bottom',
},
},
},
});
```
@ -136,8 +133,6 @@ If you try to update strings, by using API, once the UI is rendered you'll see n
We need to find the way to update the UI
-->
## Adding new language
If you want to support GrapesJS by adding a new language to our repository all you need to do is to follow steps below:
@ -150,8 +145,6 @@ If you want to support GrapesJS by adding a new language to our repository all y
1. By following comments you'll probably notice that some keys are not indicated (eg. `styleManager.properties`), for the reference you can check other locale files
1. Once you've done, you can create a new Pull Request on GitHub from your branch to `dev` by making also a reference to your issue in order to close it automatically once it's merged (your PR message should contain `Closes #1234` where 1234 is the issue ID)
## Plugin development
::: warning
@ -176,10 +169,10 @@ For your plugin specific strings, place them under the plugin name key
```js
// src/locale/en.js
export default {
'grapesjs-plugin-name': {
yourKey: 'Your value',
}
}
'grapesjs-plugin-name': {
yourKey: 'Your value',
},
};
```
In your `index.js` use the `en.js` file and add `i18n` option to allow import of other local files
@ -189,19 +182,19 @@ In your `index.js` use the `en.js` file and add `i18n` option to allow import of
import en from 'locale/en';
export default (editor, opts = {}) => {
const options = {
i18n: {},
// ...
...opts,
};
const options = {
i18n: {},
// ...
...opts,
};
editor.I18n.addMessages({
en,
...options.i18n,
});
}
// ...
editor.I18n.addMessages({
en,
...options.i18n,
});
};
```
The next step would be to compile your locale files into `<rootDir>/locale` directory to make them easily accessible by your users. This folder could be ignored in your git repository be should be deployd to the npm registry
@ -210,7 +203,6 @@ The next step would be to compile your locale files into `<rootDir>/locale` dire
Remember that you can skip all these long steps and init your project with [grapesjs-cli](https://github.com/GrapesJS/cli). This will create all the necessary folders/files/commands for you (during `init` command this step is flagged `true` by default and we recommend to keep it even in case the i18n is not required in your project)
:::
At the end, your plugin users will be able to import other locale files (if they exist) in this way
@ -14,7 +14,6 @@ This guide is referring to GrapesJS v0.19.5 or higher
[[toc]]
## Configuration
To change the default configurations you have to pass the `layerManager` option with the main configuration object.
@ -30,7 +29,6 @@ const editor = grapesjs.init({
You can check here the full list of available configuration options: [Layer Manager Config](https://github.com/GrapesJS/grapesjs/blob/master/src/navigator/config/config.ts)
Layers are a direct representation of your components, therefore they will only be available once your components are loaded in the editor (eg. you might load your project data from a remote endpoint).
In your configuration, you're able to change the global behavior of layers (eg. make all the layers not sortable) and also decide which component layer should be used as a root.
@ -50,16 +48,10 @@ const editor = grapesjs.init({
The configurations are mainly targeting the default UI provided by GrapesJS core, in case you need more control over the tree of your layers, you can read more in the [Customization](#customization) section below.
## Programmatic usage
If you need to manage layers programmatically you can use its [APIs][Layers API].
## Customization
By using the [Layers API][Layers API] you're able to replace the default UI with your own implementation.
@ -68,28 +60,28 @@ All you have to do is to indicate to the editor your intent to use a custom UI a
```js
const editor = grapesjs.init({
// ...
layerManager: {
custom: true,
// ...
layerManager: {
custom: true,
// ...
},
},
});
// Use this event to append your UI in the default container provided by GrapesJS.
// You can skip this event if you don't rely on the core panels and decide to
// place the UI in some other place.
editor.on('layer:custom', (props) => {
// props.container (HTMLElement) - The default element where you can append your UI
// props.container (HTMLElement) - The default element where you can append your UI
});
// Triggered when the root layer is changed.
editor.on('layer:root', (root) => {
// Update the root of your UI
// Update the root of your UI
});
// Triggered when a component is updated, this allows you to update specific layers.
editor.on('layer:component', (component) => {
// Update the specific layer of your UI
// Update the specific layer of your UI
});
```
@ -97,7 +89,6 @@ In the example below we'll replicate most of the default functionality with our
GrapesJS doesn't provide any default UI for the Page Manager but you can easily built one by leveraging its [APIs][Pages API]. Check the [Customization](#customization) section for more details on how to create your own Page Manager UI.
## Programmatic usage
If you need to manage pages programmatically you can use its [APIs][Pages API].
For fast plugin development, we highly recommend using [grapesjs-cli](https://github.com/GrapesJS/cli) which helps to avoid the hassle of setting up all the dependencies and configurations for development and building (no need to touch Webpack or Babel configurations). For more information check the repository.
The [Selector] allows the reuse of styles across all of your [Components] in the project (exactly what classes do in HTML) and the main goal of the Selector Manager is to collect them and indicate the current state of the selection.
::: warning
@ -15,7 +14,6 @@ This guide is referring to GrapesJS v0.17.28 or higher
[[toc]]
## Configuration
To change the default configurations you have to pass the `selectorManager` property with the main configuration object.
@ -31,7 +29,6 @@ const editor = grapesjs.init({
Check the full list of available options here: [Selector Manager Config](https://github.com/GrapesJS/grapesjs/blob/master/src/selector_manager/config/config.ts)
## Initialization
The Selector Manager starts to collect data once componenets and styles are loaded. The default UI is displayed along with the default panels provided by GrapesJS core, in case you need to setup the editor with your own panels we recommend following the [Getting Started] guide.
@ -55,6 +52,7 @@ const editor = grapesjs.init({
`,
});
```
Internally, the example above will provide to Selector Manager 3 selectors: `class-a`, `class-b` and `class-c`.
Without any selected component, the Selector Manager UI is hidden by default (along with the Style Manager). By selecting the `Element A-B-C` you will see the current selection of what will be actually styled.
@ -73,9 +71,6 @@ You can also disable specific selectors and change the state (eg. Hover) in orde
By default, selecting components with classes will indicate their selectors as target style. That means that any change in Style Manager will be applied to all components containing those **Selected** classes.
@ -99,14 +94,9 @@ This option enables also the possibility to style multiple components and the ab
With multiple selection, the Style Manager shows always styles of the last selected component.
:::
## Programmatic usage
If you need to manage your selectors programmatically you can use its [APIs][Selector API].
If you need to manage your selectors programmatically you can use its [APIs][Selector API].
## Customization
@ -116,17 +106,16 @@ All you have to do is to indicate the editor your intent to use a custom UI and
```js
const editor = grapesjs.init({
// ...
selectorManager: {
custom: true,
// ...
selectorManager: {
custom: true,
// ...
},
},
});
editor.on('selector:custom', props => {
// props.container (HTMLElement) - The default element where you can append your UI
// Here you would put the logic to render/update your UI.
editor.on('selector:custom', (props) => {
// props.container (HTMLElement) - The default element where you can append your UI
// Here you would put the logic to render/update your UI.
});
```
@ -134,17 +123,12 @@ In the example below we'll replicate most of the default functionality by using
The Storage Manager is a built-in module that allows the persistence of your project data.
::: warning
This guide requires GrapesJS v0.19.* or higher
This guide requires GrapesJS v0.19.\* or higher
:::
[[toc]]
@ -36,6 +36,7 @@ const editor = grapesjs.init({
```
In case you don't need any persistence, you can disable the module in this way:
```js
const editor = grapesjs.init({
...
@ -45,10 +46,6 @@ const editor = grapesjs.init({
Check the full list of available options here: [Storage Manager Config](https://github.com/GrapesJS/grapesjs/blob/master/src/storage_manager/config/config.ts)
## Project data
The project data is a JSON object containing all the necessary information (styles, pages, etc.) about your project in the editor and is the one used in the storage manager methods in order to store and load your project (locally or remotely in your DB/file).
@ -73,9 +71,6 @@ The editor is able to parse and use HTML/CSS code, you can use it as part of you
<!-- If necessary, the JSON can be also enriched with your data of choice, but as the data schema might differ in time we highly recommend to store them in your domain specific keys-->
## Storage strategy
Project data are automatically stored every time the amount of changes (`editor.getDirtyCount()`) reaches the number of steps before save (`editor.Storage.getStepsBeforeSave()`). On any successful store of the data, the counter of changes is reset (`editor.clearDirtyCount()`).
Most commonly the data of the project might be saved remotely on your server (DB, file, etc.) therefore you need to setup your server-side API calls in order to store/load project data.
@ -173,12 +165,9 @@ Be sure to configure properly [CORS](https://developer.mozilla.org/en-US/docs/We
Server configuration might differ case to case so usually, it's up to you to know how to configure it properly.
The default remote storage follows a simple REST API approach with project data exchanged as a JSON (`Content-Type: application/json`).
* On **load** (`GET` method), the JSON project data are expected to be returned directly in the response. As from example above, you can use `options.remote.onLoad` to extract the project data if the response contains other metadata.
* On **store** (`POST` method), the editor doesn't expect any particular result but only a valid response from the server (status code `200`).
- On **load** (`GET` method), the JSON project data are expected to be returned directly in the response. As from example above, you can use `options.remote.onLoad` to extract the project data if the response contains other metadata.
- On **store** (`POST` method), the editor doesn't expect any particular result but only a valid response from the server (status code `200`).
The Storage Manager module has also its own [set of APIs](/api/storage_manager.html) that allows you to extend and add new functionalities.
### Define new storage
Defining a new storage is a matter of passing of two asyncronous methods to the `editor.Storage.add` API. For a sake of simplicity, the example below illustrates the API usage for defining the `session` storage by using [sessionStorage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage).
@ -254,8 +236,6 @@ const editor = grapesjs.init({
});
```
### Extend storage
Among other needs, you might need to use existing storages to combine them in a more complex use case.
@ -285,7 +265,7 @@ Storage.add('remote-local', {
### Replace storage
You can also replace already defined storages with other implementations by passing the same storage type in the `Storage.add` method. You can switch, for example, the default `local`, which relies on [localStorage API], with something more scalable like [IndexedDB API].
You can also replace already defined storages with other implementations by passing the same storage type in the `Storage.add` method. You can switch, for example, the default `local`, which relies on [localStorage API], with something more scalable like [IndexedDB API].
It might also be possible that you're already using some HTTP client library (eg. [axios](https://github.com/axios/axios)) which handles for you all the necessary HTTP headers in your application (CSRF token, session data, etc.), so you can simply replace the default `remote` storage with your implementation of choice without caring about the default configurations.
@ -301,8 +281,6 @@ editor.Storage.add('remote', {
});
```
<!-- ### Examples
Here you can find some of the plugins extending the Storage Manager
@ -310,10 +288,6 @@ Here you can find some of the plugins extending the Storage Manager
* [grapesjs-indexeddb] - Storage wrapper for IndexedDB
* [grapesjs-firestore] - Storage wrapper for [Cloud Firestore](https://firebase.google.com/docs/firestore) -->
## Common use cases
### Skip initial load
@ -343,8 +317,8 @@ grapesjs.init({
},
})
```
In case `projectData` is defined, the initial storage load will be automatically skipped.
In case `projectData` is defined, the initial storage load will be automatically skipped.
Check the full list of available options here: [Style Manager Config](https://github.com/GrapesJS/grapesjs/blob/master/src/style_manager/config/config.ts)
## Initialization
The Style Manager module is organized in sectors where each sector contains a list of properties to display. The default Style Manager configuration contains already a list of default common property styles and you can use them by simply skipping the `styleManagerConfig.sectors` option.
@ -103,11 +99,12 @@ sectors: [
// Additonal `number` options
units: ['px', '%'], // Units (available only for the 'number' type)
min: 0, // Min value (available only for the 'number' type)
}
},
],
},
]
];
```
This will render the number input UI and will change the `padding` CSS property on the selected component.
The flexibility of the definition allows you to create easily different UI inputs for any possible CSS property. You're free to decide what will be the best UI for your users. If you take for example a numeric property like `font-size`, you can follow its CSS specification and define it as a `number`
@ -121,6 +118,7 @@ The flexibility of the definition allows you to create easily different UI input
min: 0,
}
```
or you can decide to show it as a `select` and make available only a defined set of values (eg. based on your Design System tokens).
```js
@ -146,7 +144,7 @@ Let's see below the list of all available default types with their relative UI a
Each **Model** describes more in detail available props and their usage.
:::
*`base` - The base type, renders as a simple text input field. **Model**: [Property](/api/property.html)
-`base` - The base type, renders as a simple text input field. **Model**: [Property](/api/property.html)
<img:src="$withBase('/sm-base-type.jpg')"/>
@ -160,11 +158,11 @@ Each **Model** describes more in detail available props and their usage.
},
```
*`color` - Same props as `base` but the UI is a color picker. **Model**: [Property](/api/property.html)
-`color` - Same props as `base` but the UI is a color picker. **Model**: [Property](/api/property.html)
<img:src="$withBase('/sm-type-color.jpg')"/>
*`number` - Number input field for numeric values. **Model**: [PropertyNumber](/api/property_number.html)
-`number` - Number input field for numeric values. **Model**: [PropertyNumber](/api/property_number.html)
<img:src="$withBase('/sm-type-number.jpg')"/>
@ -181,11 +179,12 @@ Each **Model** describes more in detail available props and their usage.
max: 100,
},
```
* `slider` - Same props as `number` but the UI is a slider. **Model**: [PropertyNumber](/api/property_number.html)
- `slider` - Same props as `number` but the UI is a slider. **Model**: [PropertyNumber](/api/property_number.html)
<img:src="$withBase('/sm-type-slider.jpg')"/>
*`select` - Select input with options. **Model**: [PropertySelect](/api/property_select.html)
-`select` - Select input with options. **Model**: [PropertySelect](/api/property_select.html)
<img:src="$withBase('/sm-type-select.jpg')"/>
@ -205,11 +204,11 @@ Each **Model** describes more in detail available props and their usage.
},
```
*`radio` - Same props as `select` but the UI is a radio button. **Model**: [PropertySelect](/api/property_select.html)
-`radio` - Same props as `select` but the UI is a radio button. **Model**: [PropertySelect](/api/property_select.html)
<img:src="$withBase('/sm-type-radio.jpg')"/>
*`composite` - This type is great for CSS shorthand properties, where the final value is a composition of multiple sub properties. **Model**: [PropertyComposite](/api/property_composite.html)
-`composite` - This type is great for CSS shorthand properties, where the final value is a composition of multiple sub properties. **Model**: [PropertyComposite](/api/property_composite.html)
<img:src="$withBase('/sm-type-composite.jpg')"/>
@ -228,7 +227,8 @@ Each **Model** describes more in detail available props and their usage.
]
},
```
* `stack` - This type is great for CSS multiple properties like `text-shadow`, `box-shadow`, `transform`, etc. **Model**: [PropertyStack](/api/property_stack.html)
- `stack` - This type is great for CSS multiple properties like `text-shadow`, `box-shadow`, `transform`, etc. **Model**: [PropertyStack](/api/property_stack.html)
<img:src="$withBase('/sm-type-stack.jpg')"/>
@ -266,26 +266,26 @@ sectors: [
units: ['px', '%'],
},
// If the property doesn't exist it will be converted to a base type
You can check if the property is available by running
```js
editor.StyleManager.getBuiltIn('property-name');
```
or get the list of all available properties with
```js
editor.StyleManager.getBuiltInAll();
```
:::
:::
## I18n
@ -308,10 +308,10 @@ grapesjs.init({
property: 'display',
default: 'block',
options: [
{id: 'block', label: 'Block'},
{id: 'inline', label: 'Inline'},
{id: 'none', label: 'None'},
]
{id: 'block', label: 'Block'},
{id: 'inline', label: 'Inline'},
{id: 'none', label: 'None'},
],
},
],
},
@ -324,7 +324,7 @@ grapesjs.init({
en: {
styleManager: {
sectors: {
'first-sector-id': 'First sector EN'
'first-sector-id': 'First sector EN',
},
properties: {
width: 'Width EN',
@ -335,22 +335,18 @@ grapesjs.init({
block: 'Block EN',
inline: 'Inline EN',
none: 'None EN',
}
}
}
}
}
},
},
},
},
},
},
});
```
## Component constraints
When you define custom components you can also indicate, via `stylable` and `unstylable` props, which CSS properties should be available for styling. In that case, the Style Manager will only show the available properties. If the sector doesn't contain any available property, it won't be shown.
When you define custom components you can also indicate, via `stylable` and `unstylable` props, which CSS properties should be available for styling. In that case, the Style Manager will only show the available properties. If the sector doesn't contain any available property, it won't be shown.
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 to their changes.
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 to their changes.
::: warning
This guide is referring to GrapesJS v0.21.9 or higher.<br><br>
@ -13,9 +13,6 @@ To get a better understanding of the content in this guide we recommend reading
[[toc]]
## 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.
@ -28,32 +25,34 @@ We can start by creating a new custom `input` component in this way:
```js
editor.Components.addType('input', {
isComponent: el => el.tagName === 'INPUT',
model: {
defaults: {
traits: [
// Strings are automatically converted to text types
'name', // Same as: { type: 'text', name: 'name' }
'placeholder',
{
type: 'select', // Type of the trait
name: 'type', // (required) The name of the attribute/property to use on component
label: 'Type', // The label you will see in Settings
options: [
{ id: 'text', label: 'Text'},
{ id: 'email', label: 'Email'},
{ id: 'password', label: 'Password'},
{ id: 'number', label: 'Number'},
]
}, {
type: 'checkbox',
name: 'required',
}],
// As by default, traits are bound to attributes, so to define
// their initial value we can use attributes
attributes: { type: 'text', required: true },
},
isComponent: (el) => el.tagName === 'INPUT',
model: {
defaults: {
traits: [
// Strings are automatically converted to text types
'name', // Same as: { type: 'text', name: 'name' }
'placeholder',
{
type: 'select', // Type of the trait
name: 'type', // (required) The name of the attribute/property to use on component
label: 'Type', // The label you will see in Settings
options: [
{ id: 'text', label: 'Text' },
{ id: 'email', label: 'Email' },
{ id: 'password', label: 'Password' },
{ id: 'number', label: 'Number' },
],
},
{
type: 'checkbox',
name: 'required',
},
],
// As by default, traits are bound to attributes, so to define
// their initial value we can use attributes
attributes: { type: 'text', required: true },
},
},
});
```
@ -65,26 +64,26 @@ If you want you can also define traits dynamically via functions, which will be
To leverage the [I18n module](I18n.html), you can refer to this schema
@ -327,10 +338,6 @@ To leverage the [I18n module](I18n.html), you can refer to this schema
}
```
## Customization
The default types should cover most of the common properties but in case you need a more advanced UI you can [define your own types](#define-new-trait-type) or even create a completely [custom Trait Manager UI](#custom-trait-manager) from scratch.
Depends on the framework, for example, in React it should be `ReactDOM.render(element, ...`
Depends on the framework, for example, in React it should be `ReactDOM.render(element, ...`
1. **Change propagation**: `sliderInst.$on('change', ev => this.onChange(ev))`<br/>
The framework should have a mechanism to subscribe to changes and the component [should expose that change](https://nightcatsama.github.io/vue-slider-component/#/api/events)<br/>
We've also used `onChange` method which comes handy when you need to trigger manually the `onEvent` event (you should never call directly `onEvent` method, but only via `onChange` when you need)
The framework should have a mechanism to subscribe to changes and the component [should expose that change](https://nightcatsama.github.io/vue-slider-component/#/api/events)<br/>
We've also used `onChange` method which comes handy when you need to trigger manually the `onEvent` event (you should never call directly `onEvent` method, but only via `onChange` when you need)