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:
- Data Safety: Prevent accidental loss of user progress.
- Seamless Experience: Reduce reliance on manual saving.
- 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
component:update: Fires when a component is updated.style:change: Fires when a style is modified.canvas:drop: Fires when an element is dropped onto the canvas.component:add: Fires when a new component is added.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 });
},
});
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.
Full Stack Software Engineer | AI Author – Credentials
Experience: 20+ Years in Software Development
Credentials: B.E. Computer, SVNIT Surat (2004)