In order to understand and fix the issue, we ask you to fill correctly all the statements/questions.
**Ifyou don't indicate a reproducible demo with relative steps to reproduce the bug, the issue might be CLOSED.**
Note:before creating a bug issue, search in GitHub Issues to check if a similar bug was already reported.
- type:checkboxes
attributes:
label:GrapesJS version
description:|
As the bug you're facing might be already fixed, we ask you to ensure to use the latest version available [](https://www.npmjs.com/package/grapesjs).
options:
- label:I confirm to use the latest version of GrapesJS
required:true
- type:input
attributes:
label:What browser are you using?
placeholder:ex. Chrome v91
validations:
required:true
- type:input
attributes:
label:Reproducible demo link
description:|
Use one of these starter templates to create your demo:[JSFiddle](https://jsfiddle.net/szLp8h4n) - [CodeSandbox](https://codesandbox.io/s/1r0w2pk1vl).
You can also indicate one of our offical demos if the bug is reproducible there.
validations:
required:true
- type:textarea
attributes:
label:Describe the bug
description:|
Indicate, step by step, how to reproduce the bug, what is the expected behavior and which is the current one.
If you're also able to create a video of the issue, that would be extremely helpful.
value:|
**Howto reproduce the bug?**
1. ...
2. ...
**Whatis the expected behavior?**
...
**Whatis the current behavior?**
...
If is necessary to execute some code in order to reproduce the bug, paste it here below:
```js
// your code here
```
validations:
required:true
- type:checkboxes
attributes:
label:Code of Conduct
description:By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/artf/grapesjs/blob/dev/CODE_OF_CONDUCT.md)
options:
- label:I agree to follow this project's Code of Conduct
@ -18,18 +18,85 @@ Once the editor is instantiated you can use its API. Before using these methods
const assetManager = editor.AssetManager;
const assetManager = editor.AssetManager;
```
```
* [add][2]
## Available Events
* [get][3]
* [getAll][4]
* `asset:open` - Asset Manager opened.
* [getAllVisible][5]
* `asset:close` - Asset Manager closed.
* [remove][6]
* `asset:remove` - Asset removed. The [Asset] is passed as an argument to the callback.
* [store][7]
* `asset:update` - Asset updated. The updated [Asset] and the object containing changes are passed as arguments to the callback.
* [load][8]
* `asset:upload:start` - Before the upload is started.
* [getContainer][9]
* `asset:upload:end` - After the upload is ended.
* [getAssetsEl][10]
* `asset:upload:error` - On any error in upload, passes the error as an argument.
* [addType][11]
* `asset:upload:response` - On upload response, passes the result as an argument.
* [getType][12]
* `asset` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback.
* [getTypes][13]
* `asset:custom` - Event for handling custom Asset Manager UI.
## Methods
* [open][2]
* [close][3]
* [isOpen][4]
* [add][5]
* [get][6]
* [getAll][7]
* [getAllVisible][8]
* [remove][9]
* [store][10]
* [load][11]
* [getContainer][12]
[Asset]: asset.html
## open
Open the asset manager.
### Parameters
* `options`**[Object][13]?** Options for the asset manager. (optional, default `{}`)
* `options.types` **[Array][14]<[String][15]>** Types of assets to show. (optional, default `['image']`)
* `options.select` **[Function][16]?** Type of operation to perform on asset selection. If not specified, nothing will happen.
### Examples
```javascript
assetManager.open({
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 && assetManager.close();
}
}
});
// with your custom types (you should have assets with those types declared)
assetManager.open({ types: ['doc'], ... });
```
## close
Close the asset manager.
### Examples
```javascript
assetManager.close();
```
## isOpen
Checks if the asset manager is open
### Examples
```javascript
assetManager.isOpen(); // true | false
```
Returns **[Boolean][17]**
## add
## add
@ -37,75 +104,75 @@ Add new asset/s to the collection. URLs are supposed to be unique
### Parameters
### Parameters
* `asset`**([string][14] | [Object][15] | [Array][16]<[string][14]> | [Array][16]<[Object][15]>)** URL strings or an objects representing the resource.
* `asset`**([String][15] | [Object][13] | [Array][14]<[String][15]> | [Array][14]<[Object][13]>)** URL strings or an objects representing the resource.
* `block:drag` - Dragging block, the block's model and the drag event are passed as arguments
* `block:drag` - Dragging block, the block's model and the drag event are passed as arguments
* `block:drag:stop` - Dragging of the block is stopped. As agruments for the callback you get, the dropped component model (if dropped successfully) and the model of the block
* `block:drag:stop` - Dragging of the block is stopped. As agruments for the callback you get, the dropped component model (if dropped successfully) and the model of the block
### Assets
* `asset:add` - New asset added
* `asset:remove` - Asset removed
* `asset:upload:start` - Before the upload is started
* `asset:upload:end` - After the upload is ended
* `asset:upload:error` - On any error in upload, passes the error as an argument
* `asset:upload:response` - On upload response, passes the result as an argument
### Keymaps
### Keymaps
* `keymap:add` - New keymap added. The new keyamp object is passed as an argument
* `keymap:add` - New keymap added. The new keyamp object is passed as an argument
@ -108,11 +99,6 @@ By changing `result.content` you're able to customize what is dropped
* `rte:enable` - RTE enabled. The view, on which RTE is enabled, is passed as an argument
* `rte:enable` - RTE enabled. The view, on which RTE is enabled, is passed as an argument
* `rte:disable` - RTE disabled. The view, on which RTE is disabled, is passed as an argument
* `rte:disable` - RTE disabled. The view, on which RTE is disabled, is passed as an argument
### Modal
* `modal:open` - Modal is opened
* `modal:close` - Modal is closed
### Commands
### Commands
* `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview'))
* `run:{commandName}` - Triggered when some command is called to run (eg. editor.runCommand('preview'))
@ -123,17 +109,25 @@ By changing `result.content` you're able to customize what is dropped
* `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback
* `run` - Triggered on run of any command. The id and the result are passed as arguments to the callback
* `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback
* `stop` - Triggered on stop of any command. The id and the result are passed as arguments to the callback
### Assets
Check the [Assets][2] module.
### Modal
Check the [Modal][3] module.
### Devices
### Devices
Check the [Devices][2] module.
Check the [Devices][4] module.
### Parser
### Parser
Check the [Parser][3] module.
Check the [Parser][5] module.
### Pages
### Pages
Check the [Pages][4] module.
Check the [Pages][6] module.
### General
### General
@ -149,7 +143,7 @@ Returns configuration object
### Parameters
### Parameters
* `prop`**[string][5]?** Property name
* `prop`**[string][7]?** Property name
Returns **any** Returns the configuration object or
Returns **any** Returns the configuration object or
the value of the specified property
the value of the specified property
@ -160,12 +154,12 @@ Returns HTML built inside canvas
@ -18,6 +18,14 @@ Once the editor is instantiated you can use its API. Before using these methods
const modal = editor.Modal;
const modal = editor.Modal;
```
```
## Available Events
* `modal:open` - Modal is opened
* `modal:close` - Modal is closed
* `modal` - Event triggered on any change related to the modal. An object containing all the available data about the triggered event is passed as an argument to the callback.
## Methods
* [open][2]
* [open][2]
* [close][3]
* [close][3]
* [isOpen][4]
* [isOpen][4]
@ -40,12 +48,28 @@ Open the modal window
* `opts.content` **([String][12] | [HTMLElement][13])?** Content to set for the modal
* `opts.content` **([String][12] | [HTMLElement][13])?** Content to set for the modal
* `opts.attributes` **[Object][11]?** Updates the modal wrapper with custom attributes
* `opts.attributes` **[Object][11]?** Updates the modal wrapper with custom attributes
### Examples
```javascript
modal.open({
title: 'My title',
content: 'My content',
attributes: { class: 'my-class' },
});
```
Returns **this**
Returns **this**
## close
## close
Close the modal window
Close the modal window
### Examples
```javascript
modal.close();
```
Returns **this**
Returns **this**
## onceClose
## onceClose
@ -55,7 +79,15 @@ The callback will be called one only time
### Parameters
### Parameters
* `clb`**[Function][14]**
* `clb`**[Function][14]** Callback to call
### Examples
```javascript
modal.onceClose(() => {
console.log('The modal is closed');
});
```
Returns **this**
Returns **this**
@ -66,7 +98,15 @@ The callback will be called one only time
### Parameters
### Parameters
* `clb`**[Function][14]**
* `clb`**[Function][14]** Callback to call
### Examples
```javascript
modal.onceOpen(() => {
console.log('The modal is opened');
});
```
Returns **this**
Returns **this**
@ -74,6 +114,12 @@ Returns **this**
Checks if the modal window is open
Checks if the modal window is open
### Examples
```javascript
modal.isOpen(); // true | false
```
Returns **[Boolean][15]**
Returns **[Boolean][15]**
## setTitle
## setTitle
@ -82,12 +128,17 @@ Set the title to the modal window
### Parameters
### Parameters
* `title`**[string][12]** Title
* `title`**([string][12] | [HTMLElement][13])** Title
### Examples
### Examples
```javascript
```javascript
modal.setTitle('New title');
// pass a string
modal.setTitle('Some title');
// or an HTMLElement
const el = document.createElement('div');
el.innerText = 'New title';
modal.setTitle(el);
```
```
Returns **this**
Returns **this**
@ -96,7 +147,13 @@ Returns **this**
Returns the title of the modal window
Returns the title of the modal window
Returns **[string][12]**
### Examples
```javascript
modal.getTitle();
```
Returns **([string][12] | [HTMLElement][13])**
## setContent
## setContent
@ -109,7 +166,12 @@ Set the content of the modal window
// Content to add where there is no assets to show
// eg. 'No <b>assets</b> here, drag to upload'
noAssets: '',
// Upload endpoint, set `false` to disable upload
// upload: 'https://endpoint/upload/assets',
// upload: false,
upload: 0,
// The name used in POST to pass uploaded files
uploadName: 'files',
// Custom headers to pass with the upload request
headers: {},
// Custom parameters to pass with the upload request, eg. csrf token
params: {},
// The credentials setting for the upload request, eg. 'include', 'omit'
credentials: 'include',
// Allow uploading multiple files per request.
// If disabled filename will not have '[]' appended
multiUpload: true,
// If true, tries to add automatically uploaded assets.
// To make it work the server should respond with a JSON containing assets
// in a data key, eg:
// {
// data: [
// 'https://.../image.png',
// ...
// {src: 'https://.../image2.png'},
// ...
// ]
// }
autoAdd: 1,
// Text on upload input
uploadText: 'Drop files here or click to upload',
// Label for the add button
addBtnText: 'Add image',
// Custom uploadFile function
// @example
// uploadFile: (e) => {
// var files = e.dataTransfer ? e.dataTransfer.files : e.target.files;
// // ...send somewhere
// }
uploadFile: '',
// Handle the image url submit from the built-in 'Add image' form
// @example
// handleAdd: (textFromInput) => {
// // some check...
// editor.AssetManager.add(textFromInput);
// }
handleAdd: '',
// Enable an upload dropzone on the entire editor (not document) when dragging
// files over it
dropzone: 1,
// Open the asset manager once files are been dropped via the dropzone
openAssetsOnDrop: 1,
// Any dropzone content to append inside dropzone element
dropzoneContent: '',
// Default title for the asset manager modal
modalTitle: 'Select Image',
```
Sometimes the code gets ahead of the docs, therefore we'd suggest to keep an eye at the current state of configurations by checking the dedicated source file [Asset Manager Config](https://github.com/artf/grapesjs/blob/dev/src/asset_manager/config/config.js)
Now you should be able to change the image of the component.
Now you should be able to change the image of the component.
-->
## Customization
## 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
If you want to customize the Asset Manager after the initialization you have to use its [APIs](API-Asset-Manager)
```js
const editor = grapesjs.init({
...
assetManager: {
...
// Upload endpoint, set `false` to disable upload, default `false`
upload: 'https://endpoint/upload/assets',
// The name used in POST to pass uploaded files, default: `'files'`
uploadName: 'files',
...
},
...
});
```
### 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:
```js
{
data: [
'https://.../image.png',
// ...
{
src: 'https://.../image2.png',
type: 'image',
height: 100,
width: 200,
},
// ...
]
}
```
<!-- Deprecated
### Setup Dropzone
There is another helper which improves the uploading of assets: A full-width editor dropzone.
In case you want to update or remove an asset, you can make use of this methods
```js
// Get the asset via its `src`
const asset = am.get('http://.../img.jpg');
// Update asset property
asset.set({ src: 'http://.../new-img.jpg' });
// Remove asset
am.remove(asset); // or via src, am.remove('http://.../new-img.jpg');
```
For more APIs methods check out the [API Reference](API-Asset-Manager)
For more APIs methods check out the [API Reference](API-Asset-Manager)
### Custom select logic
::: warning
This section is referring to GrapesJS v0.17.26 or higher
:::
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();
}
}
});
```
## 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 defualt UI.
All you have to do is to indicate the editor your intent to use a custom UI and then subscribe to the `asset:custom` event that will give you all the information on any requested change.
```js
const editor = grapesjs.init({
// ...
assetManager: {
// ...
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
The example above is the right way if you need to replace the default UI, but as you might notice we append the mounted element to the container `props.container.appendChild(this.$el);`.
This is required as the Asset Manager, by default, is placed in the [Modal](/modules/Modal.html).
How to approach the case when your Asset Manager is a completely independent/external module (eg. should be showed in its own custom modal)? Not a problem, you can bind the Asset Manager state via `assetManager.custom.open`.
```js
const editor = grapesjs.init({
// ...
assetManager: {
// ...
custom: {
open(props) {
// `props` are the same used in `asset:custom` event
// ...
// Init and open your external Asset Manager
// ...
// IMPORTANT:
// When the external library is closed you have to comunicate
// this state back to the editor, otherwise GrapesJS will think
It's important to declare also the `close` function, the editor should be able to close the Asset Manager via `am.close()`.
<!--
### Define new Asset type
### Define new Asset type
Generally speaking, images aren't the only asset you'll use, it could be a `video`, `svg-icon`, or any other kind of `document`. Each type of asset is applied in our templates/pages differently. If you need to change the image of the Component all you need is another `url` in `src` attribute. However In case of a `svg-icon`, its not the same, you might want to replace the element with a new `<svg>` content. Besides this you also have to deal with the presentation/preview of the asset inside the panel/modal. For example, showing a thumbnail for big images or the possibility to preview videos.
Generally speaking, images aren't the only asset you'll use, it could be a `video`, `svg-icon`, or any other kind of `document`. Each type of asset is applied in our templates/pages differently. If you need to change the image of the Component all you need is another `url` in `src` attribute. However In case of a `svg-icon`, its not the same, you might want to replace the element with a new `<svg>` content. Besides this you also have to deal with the presentation/preview of the asset inside the panel/modal. For example, showing a thumbnail for big images or the possibility to preview videos.
@ -414,9 +533,6 @@ am.addType('svg-icon', {
```
```
### Extend Asset Types
### Extend Asset Types
Extending asset types is basically the same as adding them, you can choose what type to extend and how.
Extending asset types is basically the same as adding them, you can choose what type to extend and how.
@ -463,119 +579,7 @@ am.addType('image', {
}
}
},
},
})
})
```
``` -->
## Uploading assets
Asset Manager includes 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
let editor = grapesjs.init({
...
assetManager: {
...
// Upload endpoint, set `false` to disable upload, default `false`
upload: 'https://endpoint/upload/assets',
// The name used in POST to pass uploaded files, default: `'files'`
uploadName: 'files',
...
},
...
});
```
### 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 blob 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:
```js
{
data: [
'https://.../image.png',
// ...
{
src: 'https://.../image2.png',
type: 'image',
height: 100,
width: 200,
},
// ...
]
}
```
### Setup Dropzone
There is another helper which improves the uploading of assets: A full-width editor dropzone.
The Component is a base element of the template. It might be something simple and atomic like an image or a text box, but also complex structures, more probably composed by other components, like sections or pages. The concept of the component was made to allow the developer to bind different behaviors to different elements. For example, opening the Asset Manager on double click of the image is a custom behavior binded to that particular type of element.
The Component is a base element of the template. It might be something simple and atomic like an image or a text box, but also complex structures, more probably composed by other components, like sections or pages. The concept of the component was made to allow the developer to bind different behaviors to different elements. For example, opening the Asset Manager on double click of the image is a custom behavior bound to that particular type of element.
::: warning
::: warning
This guide is referring to GrapesJS v0.15.8 or higher
This guide is referring to GrapesJS v0.15.8 or higher
The **Modal** module allows to easily display content in a dialog window.
::: warning
This guide is referring to GrapesJS v0.17.26 or higher
:::
[[toc]]
## Basic usage
You can easily display your content by calling a single API call.
```js
// Init editor
const editor = grapesjs.init({ ... });
// Open modal
const openModal = () => {
editor.Modal.open({
title: 'My title', // string | HTMLElement
content: 'My content', // string | HTMLElement
});
};
// Create a simple custom button that will open the modal
document.body.insertAdjacentHTML('afterbegin',`
<buttononclick="openModal()">Open Modal</button>
`);
```
## Using API
By using other [available APIs](/api/modal_dialog.html) you have full control of the modal (eg. updating content/title, closing the modal, etc.).
Here are a few examples:
```js
const { Modal } = editor;
// Close the modal
Modal.close();
// Check if the modal is open
Modal.isOpen();
// Update title
Modal.setTitle('New title');
// Update content
Modal.setContent('New content');
// Execute one-time callback on modal close
Modal.onceClose(() => {
console.log('My last modal is closed');
});
```
## Customization
The modal can be fully customized and you have different available options.
The fastest and the easiest one is to use your specific CSS for the modal element. With a few lines of CSS your modal can be completely adapted to your choices.
```css
.gjs-mdl-dialog {
background-color: white;
color: #333;
}
```
In case you have to customize a specific modal differently, you can rely on your custom class attributes.
```js
editor.Modal.open({
title: 'My title',
content: 'My content',
attributes: {
class: 'my-small-modal',
},
});
```
```css
.my-small-modal .gjs-mdl-dialog {
max-width: 300px;
}
```
::: warning
Your custom CSS has to be loaded after the GrapesJS one.
:::
### Custom Modal
For more advanced usage, you can completely replace the default modal with one of your own. All you have to do is to indicate the editor your intent to use a custom modal and then subscribe to the `modal` event that will give you all the information on any requested change.
```js
const editor = grapesjs.init({
// ...
modal: { custom: true },
});
editor.on('modal', props => {
// The `props` will contain all the information you need in order to update your custom modal.
// props.open (boolean) - Indicates if the modal should be open