GrapesJS Web Builder Framework

repository·dev·Indexed 10 days ago

https://github.com/GrapesJS/grapesjs

A multi-purpose Web Builder Framework for creating drag-and-drop editors for HTML-like content, including webpages, newsletters (MJML), native mobile and desktop applications, and PDFs. It features a comprehensive API for extensions, a dedicated CLI for plugin development, and pre-configured presets for common use cases.

Tokens
150.6K
Snippets
572
Records
679
Agent score
96%

What's inside GrapesJS

  1. What is GrapesJS?

    dev

    GrapesJS is a multi-purpose Web Builder Framework designed to create drag-and-drop builders for any content with an HTML-like structure. While often used for web pages, it is extensible enough to build editors for:

    • Newsletters (e.g., using MJML)
    • Native Mobile Applications (e.g., React Native)
    • Native Desktop Applications (e.g., Vuido)
    • PDFs (e.g., React PDF)

    It provides the tools to allow non-technical users to create complex templates without writing code.

  2. Add and manage Blocks

    dev

    Blocks are reusable pieces of HTML (images, buttons, sections) that users can drag into the canvas. You can define blocks during initialization via the blockManager configuration or add them dynamically using the BlockManager API.

    Block Configuration Options:

    • id: (Mandatory) Unique identifier for the block.
    • label: The display name (supports HTML/SVG).
    • attributes: HTML attributes for the block element.
    • content: The HTML string or a JSON object representing the component structure.
    • select: If true, the component is selected immediately upon being dropped.
    • activate: If true, triggers the active event on the dropped component (useful for opening the Asset Manager).
    // Initial configuration
    const editor = grapesjs.init({
      blockManager: {
        appendTo: '#blocks',
        blocks: [
          {
            id: 'section',
            label: '<b>Section</b>',
            content: '<section><h1>Title</h1></section>',
          },
          {
            id: 'image',
            label: 'Image',
            select: true,
            content: { type: 'image' },
            activate: true,
          },
        ],
      },
    });
    
    // Dynamic addition
    editor.BlockManager.add('my-block-id', {
      label: '...',
      category: '...',
    });
  3. How Component Recognition works

    dev

    When you pass an HTML string to the editor, GrapesJS performs Component Recognition to assign a type to each element.

    1. Parsing: The HTML is transformed into a Component Definition (a lightweight Virtual DOM-like JSON structure containing tagName, attributes, and components).
    2. Type Assignment: The editor iterates over the Component Type Stack (an array of component types). For each parsed element, it checks the stack from top to bottom.
    3. Matching: The first component type in the stack that returns a truthy value from its isComponent method (or isParsedNode if defined) is assigned to that element.
    4. Default: If no match is found, the type defaults to 'default'.

    Custom component types added to the editor are placed at the top of the stack, allowing them to override default behavior.

  4. The Component object

    dev

    A Component object represents a single node in the GrapesJS template structure. Updating a component's properties immediately reflects changes on the canvas and in the exported code. The component acts as a node in a tree structure, where changes to a node propagate through the model.

    component.set({
     tagName: 'span',
     attributes: { ... },
     removable: false,
    });
    component.get('tagName');
    // -> 'span'
  5. Define Style Manager sectors and properties

    dev

    The Style Manager is organized into sectors. Each sector contains a list of properties.

    Sector Configuration

    • name: The display name of the sector.
    • id: (Optional) A unique identifier to access the sector via API. If not provided, it is generated from the name.
    • open: (Optional) Boolean indicating if the sector should be open by default.
    • properties: An array of property definitions.

    Property Configuration

    Each property defines a CSS property to change. Common options include:

    • type: The UI type (e.g., number, color, select).
    • label: The display label for the property.
    • property: The actual CSS property name (e.g., padding, font-size).
    • default: The default value.
    • id: (Optional) The property ID. If missing, it defaults to the property value.

    Example of a numeric padding property:

    {
      type: 'number',
      label: 'Padding',
      property: 'padding',
      default: '0',
      units: ['px', '%'],
      min: 0,
    }
    grapesjs.init({
      styleManager: {
        sectors: [
          {
            name: 'First sector',
            properties: [],
            // id: 'first-sector',
            // open: true,
          },
          {
            name: 'Second sector',
            open: false,
            properties: [],
          },
        ],
      },
    });
  6. Understand the storage strategy and dirty count

    dev

    GrapesJS uses a 'dirty count' mechanism to manage automatic saves.

    1. Every change increments the dirty count (accessible via editor.getDirtyCount()).
    2. When the dirty count reaches the value defined in stepsBeforeSave (accessible via editor.Storage.getStepsBeforeSave()), the storage method is triggered.
    3. Upon a successful save, the counter is reset using editor.clearDirtyCount().

    You can also trigger storage operations manually using asynchronous methods:

    // Manually store data
    const storedProjectData = await editor.store();
    
    // Manually load data
    const loadedProjectData = await editor.load();
  7. What are Canvas Spots

    dev
    Canvas Spots are elements drawn on top of the GrapesJS canvas. They are primarily used for rendering information or managing components that are rendered within the canvas area. They act as an overlay layer that can represent various UI elements or metadata related to the canvas content.
  8. Define a new Asset type

    dev

    You can define new asset types (e.g., video, svg-icon) by using am.addType(). This allows GrapesJS to recognize specific data formats and handle them correctly in the UI and the canvas.

    A type definition consists of three main parts:

    1. isType(value): A function that determines if a given value (string or object) matches this type. It should return an object containing the type name and any extra data (like svgContent).
    2. view: Defines how the asset is rendered in the Asset Manager UI and how it interacts with the canvas.
      • getPreview(): Returns the HTML for the asset thumbnail.
      • getInfo(): Returns the HTML for the asset description/info.
      • updateTarget(target): Defines how the asset is applied to the selected component (e.g., setting src or content).
    3. model (optional): Allows you to define default properties and business logic for the asset.
    // 1. Define the type
    am.addType('svg-icon', {
      isType(value) {
        if (value.substring(0, 5) == '<svg ') {
          return {
            type: 'svg-icon',
            svgContent: value
          };
        }
        return null;
      },
      view: {
        getPreview() {
          return `<div style="text-align: center">${this.model.get('svgContent')}</div>`;
        },
        updateTarget(target) {
          const svg = this.model.get('svgContent');
          if (target.get('type') == 'image') {
            target.set('src', `data:image/svg+xml;base64,${btoa(svg)}`);
          } else {
            target.set('content', svg);
          }
        }
      },
      model: {
        defaults: {
          type: 'svg-icon',
          svgContent: '',
          name: 'Default SVG Name',
        }
      }
    });
    
    // 2. Add an asset of that type
    am.add('<svg ...>...</svg>');
  9. Implement a Custom Modal

    dev

    To completely replace the default GrapesJS modal with your own (e.g., a Bootstrap modal), follow these two steps:

    1. Initialize the editor with the modal: { custom: true } configuration.
    2. Listen to the modal event to receive updates and control the lifecycle of your custom UI.

    The props object in the event listener contains:

    • props.open (boolean): Indicates if the modal should be open.
    • props.title (Node): The modal title.
    • props.content (Node): The modal content.
    • props.attributes (Object): Custom attributes (e.g., class).
    • props.close (Function): A callback to trigger programmatic closing.
    const editor = grapesjs.init({
      // ...
      modal: { custom: true },
    });
    
    editor.on('modal', (props) => {
      // Use props to update your custom UI logic
      // props.open, props.title, props.content, props.attributes, props.close
    });
  10. Define block content using HTML strings

    dev

    You can use HTML strings for the content property. This is useful for static site builders where users paste templates (e.g., Tailwind components). GrapesJS will parse the HTML and convert elements into default components.

    To bind logic to these parsed elements without changing the HTML, use the isComponent method in Components.addType to detect elements by class or attribute. Alternatively, you can use data-gjs-* attributes (like data-gjs-type) directly in the HTML string to declare component types.

    // Option 1: Using isComponent to bind logic to HTML strings
    editor.Components.addType('cmp-Y', {
      isComponent: (el) => el.classList?.contains('el-Y'),
      model: {
        defaults: {
          name: 'Component Y',
          draggable: '.el-X',
        },
      },
    });
    
    // Option 2: Using data-gjs-type in the HTML string
    const block = {
      content: `<div class="el-X">
        <div data-gjs-type="cmp-Y" class="el-Y">Element A</div>
      </div>`
    };
  11. Enable Component-first selectors

    dev

    By default, the Selector Manager targets all components that share the same set of selected classes. If you want to target specific, individual components instead, enable the componentFirst option.

    When componentFirst is enabled:

    • You can select single components as specific style targets.
    • You can style multiple components.
    • You can sync common selectors with the current component styles using the refresh icon.

    Warning: In multiple selection mode, the Style Manager will always display the styles of the last selected component.

    const editor = grapesjs.init({
      // ...
      selectorManager: {
        componentFirst: true,
      },
    });
  12. Manage plugins dynamically at runtime

    dev

    Since GrapesJS v0.23.1, you can add, remove, and inspect plugins at runtime using the editor.Plugins module.

    When adding plugins dynamically, you must use the object descriptor form with an explicit id so the plugin can be identified later.

    Key Capabilities:

    • Automatic Cleanup: GrapesJS automatically removes editor-level registrations (like blocks, components, and keymaps) added by a plugin when that plugin is removed. It does not remove persisted content like pages or canvas components.
    • Manual Teardown: If a plugin requires custom cleanup (e.g., clearing intervals or event listeners), it should return a cleanup function.

    Plugin Management API:

    • editor.Plugins.add({ id, plugin }): Adds a new plugin.
    • editor.Plugins.get(id): Returns the plugin if it exists.
    • editor.Plugins.getAll(): Returns all active plugins.
    • editor.Plugins.remove(id): Removes the plugin and triggers automatic cleanup.
    // Initializing with an ID
    const editor = grapesjs.init({
      // ...
      plugins: [
        { id: 'my-plugin-1', plugin: usePlugin(myPlugin, { opt1: 'A' }) }
      ],
    });
    
    // Adding dynamically
    const plugin = editor.Plugins.add({
      id: 'my-plugin-2',
      plugin: usePlugin(myPlugin, { opt1: 'A' }),
    });
    
    // Checking and removing
    const hasPlugin = !!editor.Plugins.get('my-plugin-2');
    const allPlugins = editor.Plugins.getAll();
    editor.Plugins.remove('my-plugin-2');
    
    // Custom cleanup logic in a plugin
    const myPlugin: Plugin = (editor) => {
      const interval = setInterval(() => {
        // ...
      }, 1000);
    
      return ({ cleanup }) => {
        cleanup();
        clearInterval(interval);
      };
    };