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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css">
<script src="https://unpkg.com/grapesjs"></script>
<link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css"> <script src="https://unpkg.com/grapesjs"></script>


Using npm:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install grapesjs
npm install grapesjs
npm install grapesjs

JavaScript:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import grapesjs from 'grapesjs';
import grapesjs from 'grapesjs';
import grapesjs from 'grapesjs';

Initializing the Editor

Create a container for the editor and initialize it.

HTML:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div id="editor"></div>
<div id="editor"></div>
<div id="editor"></div>

JavaScript:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const editor = grapesjs.init({
container: '#editor',
fromElement: true,
width: '100%',
height: '100%',
storageManager: false, // Disable built-in storage
});
const editor = grapesjs.init({ container: '#editor', fromElement: true, width: '100%', height: '100%', storageManager: false, // Disable built-in storage });
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
editor.on('component:update', saveChanges);
editor.on('style:change', saveChanges);
editor.on('canvas:drop', saveChanges);
editor.on('component:update', saveChanges); editor.on('style:change', saveChanges); editor.on('canvas:drop', saveChanges);
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const debounce = (func, delay) => {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), delay);
};
};
const debounce = (func, delay) => { let timeout; return (...args) => { clearTimeout(timeout); timeout = setTimeout(() => func(...args), delay); }; };
const debounce = (func, delay) => {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => func(...args), delay);
  };
};

Wrap the saveChanges function:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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']);
}
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']); }
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div id="autosave-status">All changes saved</div>
<div id="autosave-status">All changes saved</div>
<div id="autosave-status">All changes saved</div>

JavaScript for Updating Status

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
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);
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
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);
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
editor.BlockManager.add('custom-block', {
label: 'Custom Block',
content: '<div class="my-block">This is a custom block!</div>',
category: 'Basic',
});
editor.BlockManager.add('custom-block', { label: 'Custom Block', content: '<div class="my-block">This is a custom block!</div>', category: 'Basic', });
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
fetch('load.php')
.then((response) => response.json())
.then((data) => {
editor.addComponents(data.html);
editor.setStyle(data.css);
});
fetch('load.php') .then((response) => response.json()) .then((data) => { editor.addComponents(data.html); editor.setStyle(data.css); });
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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
editor.Commands.add('save-command', {
run: () => {
const html = editor.getHtml();
const css = editor.getCss();
console.log('Saved content:', { html, css });
},
});
editor.Commands.add('save-command', { run: () => { const html = editor.getHtml(); const css = editor.getCss(); console.log('Saved content:', { html, css }); }, });
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
editor.Panels.addButton('options', {
id: 'save-button',
className: 'btn-save',
label: 'Save',
command: 'save-command',
});
editor.Panels.addButton('options', { id: 'save-button', className: 'btn-save', label: 'Save', command: 'save-command', });
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