GrapesJS Tutorial: With Vanilla JavaScript and PHP Backend

GrapesJS is an open-source, drag-and-drop web builder framework used to create dynamic templates for web pages, emails, and more. This tutorial will walk you through the setup and integration of GrapesJS with a modern autosave system using event listeners and a PHP backend.


Why Use GrapesJS?

GrapesJS provides a visual editor with features such as:

  • Drag-and-drop interface
  • Component-based structure
  • Customizable blocks and styles

Adding an autosave feature ensures:

  1. Data Safety: Prevent accidental loss of user progress.
  2. Seamless Experience: Reduce reliance on manual saving.
  3. Enhanced Collaboration: Keep templates up-to-date across users.

Let’s dive into building an efficient GrapesJS editor with autosave functionality.


Step 1: Setting Up GrapesJS

Including GrapesJS

You can include GrapesJS using a CDN or npm.

Using CDN:



Using npm:

npm install grapesjs

JavaScript:

import grapesjs from 'grapesjs';

Initializing the Editor

Create a container for the editor and initialize it.

HTML:

<div id="editor"></div>

JavaScript:

const editor = grapesjs.init({
  container: '#editor',
  fromElement: true,
  width: '100%',
  height: '100%',
  storageManager: false, // Disable built-in storage
});

This sets up the GrapesJS editor with a blank canvas.


Step 2: Detecting Changes with Event Listeners

GrapesJS provides event listeners to detect changes. Instead of relying on outdated polling techniques like setInterval(), you can subscribe to events.

Key Events

  1. component:update: Fires when a component is updated.
  2. style:change: Fires when a style is modified.
  3. canvas:drop: Fires when an element is dropped onto the canvas.
  4. component:add: Fires when a new component is added.
  5. component:remove: Fires when a component is removed.

Example: Subscribing to Events

editor.on('component:update', saveChanges);
editor.on('style:change', saveChanges);
editor.on('canvas:drop', saveChanges);

These event listeners call the saveChanges function when specific actions occur.


Step 3: Implementing Autosave with Debounce

Autosave should not trigger excessive requests during rapid user interactions. Use a debounce function to delay execution until the user stops performing actions for a specified period.

Debounce Function

const debounce = (func, delay) => {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), delay);
  };
};

Wrap the saveChanges function:

const saveChanges = debounce(() => {
  const html = editor.getHtml();
  const css = editor.getCss();

  fetch('save.php', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ html, css }),
  })
    .then((response) => {
      if (response.ok) {
        console.log('Autosave successful');
      } else {
        console.error('Autosave failed');
      }
    })
    .catch((error) => {
      console.error('Autosave error:', error);
    });
}, 1000); // Save after 1 second of inactivity

This ensures autosave only triggers when the user pauses.


Step 4: Setting Up the PHP Backend

Create a PHP script to handle autosave requests.

PHP Script (save.php)

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $data = json_decode(file_get_contents('php://input'), true);

    $html = $data['html'] ?? '';
    $css = $data['css'] ?? '';

    // Save HTML and CSS to files
    file_put_contents('saved_template.html', $html);
    file_put_contents('saved_template.css', $css);

    echo json_encode(['status' => 'success', 'message' => 'Changes saved successfully']);
} else {
    http_response_code(405);
    echo json_encode(['status' => 'error', 'message' => 'Method not allowed']);
}

This script saves the editor's content to files. You can modify it to store the data in a database.


Step 5: Providing User Feedback

Feedback improves user experience by indicating when changes are saved or if an error occurs.

HTML for Status Display

<div id="autosave-status">All changes saved</div>

JavaScript for Updating Status

const updateStatus = (message, success = true) => {
  const status = document.getElementById('autosave-status');
  status.textContent = message;
  status.style.color = success ? 'green' : 'red';
};

const saveChanges = debounce(() => {
  const html = editor.getHtml();
  const css = editor.getCss();

  updateStatus('Saving...');

  fetch('save.php', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ html, css }),
  })
    .then((response) => {
      if (response.ok) {
        updateStatus('All changes saved');
      } else {
        updateStatus('Save failed', false);
      }
    })
    .catch(() => {
      updateStatus('Network error', false);
    });
}, 1000);

Step 6: Tracking Changes Efficiently

For larger projects, you can manage change tracking with a simple global flag.

Custom Change Tracking

let isDirty = false;

const markDirty = () => {
  isDirty = true;
  saveChanges();
};

editor.on('component:update', markDirty);
editor.on('style:change', markDirty);
editor.on('canvas:drop', markDirty);

const saveChanges = debounce(() => {
  if (!isDirty) return;

  const html = editor.getHtml();
  const css = editor.getCss();

  fetch('save.php', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ html, css }),
  })
    .then((response) => {
      if (response.ok) {
        console.log('Autosave successful');
        isDirty = false;
      } else {
        console.error('Autosave failed');
      }
    })
    .catch((error) => {
      console.error('Autosave error:', error);
    });
}, 1000);

This method ensures autosave only occurs when necessary, keeping requests efficient.


Step 7: Advanced Customization of GrapesJS

GrapesJS is highly customizable. Here are a few advanced features you can implement:

Custom Blocks

You can create custom blocks to extend GrapesJS's default functionality.

editor.BlockManager.add('custom-block', {
  label: 'Custom Block',
  content: '
This is a custom block!
', category: 'Basic', });

Dynamic Loading of Content

If you have existing content, you can dynamically load it into the editor:

fetch('load.php')
  .then((response) => response.json())
  .then((data) => {
    editor.addComponents(data.html);
    editor.setStyle(data.css);
  });

Custom Commands

You can add custom commands to extend GrapesJS's functionality.

editor.Commands.add('save-command', {
  run: () => {
    const html = editor.getHtml();
    const css = editor.getCss();
    console.log('Saved content:', { html, css });
  },
});

Panels and Buttons

You can add custom panels and buttons to the GrapesJS interface:

editor.Panels.addButton('options', {
  id: 'save-button',
  className: 'btn-save',
  label: 'Save',
  command: 'save-command',
});

By leveraging GrapesJS’s event system and modern JavaScript techniques, you can implement an efficient autosave feature with a PHP backend. This approach avoids outdated practices and ensures a seamless user experience while maintaining scalability and reliability. You can further extend GrapesJS with custom blocks, commands, and dynamic content loading, creating a robust and versatile template editor. With these steps, you have a complete guide to mastering GrapesJS and enhancing user workflows.

Total
0
Shares
Previous Post

How Can You Create a HubSpot Module for Your Template?

Next Post

What Are the Must-Know React Concepts for Interview Preparation?

Related Posts