Save Lifecycle Deep Dive
Understanding Hyperclay’s save lifecycle is crucial for building robust applications. This guide explores how saves work, when they trigger, and how to control the process.
The Save Lifecycle
- DOM Change Detected → Debounce Timer
- beforeSave Callbacks → Run cleanup functions
- onbeforesave Attributes → Execute element-specific cleanup
- Strip Admin Elements → Remove edit-mode-only content
- Serialize DOM → Convert to HTML string
- POST to Server → Send to Hyperclay
- Save Complete → Callback executed
When Saves Trigger
Automatic Triggers
- DOM mutations (structure changes)
- Attribute changes
- Text content changes
- Form value changes (with
persist
)
Manual Triggers
- Keyboard: Cmd/Ctrl+S
- Elements with
trigger-save
- Calling
hyperclay.savePage()
What Doesn’t Trigger Saves
- Elements with
mutations-ignore
- Elements with
save-ignore
- Transient UI states (hover, focus)
- CSS-only changes
The onbeforesave Attribute
Execute JavaScript before the page is saved to clean up or transform the DOM.
Basic Examples
<!-- Remove temporary UI -->
<div class="notification" onbeforesave="this.remove()">
Temporary message
</div>
<!-- Reset form values -->
<form onbeforesave="this.reset()">
<input type="text" value="temp">
</form>
<!-- Clean attributes -->
<div class="modal" open onbeforesave="this.removeAttribute('open')">
Modal content
</div>
Common Patterns
<!-- Conditional cleanup -->
<div onbeforesave="if (this.dataset.status === 'draft') this.remove()">
Draft content
</div>
<!-- Sort before saving -->
<ul onbeforesave="[...this.children].sort((a,b) => a.textContent.localeCompare(b.textContent)).forEach(el => this.appendChild(el))">
<li>Item C</li>
<li>Item A</li>
<li>Item B</li>
</ul>
<!-- Save scroll position -->
<div class="scrollable"
onbeforesave="this.dataset.scrollTop = this.scrollTop">
Long content...
</div>
Global beforeSave Hook
For application-wide transformations:
hyperclay.beforeSave = function(doc) {
// Remove all temporary elements
doc.querySelectorAll('[data-temp]').forEach(el => el.remove());
// Clean empty containers
doc.querySelectorAll('.container:empty').forEach(el => el.remove());
// Add save timestamp
doc.body.dataset.lastSaved = new Date().toISOString();
};
Common Save Patterns
Progressive Enhancement
<!-- Base content (what gets saved) -->
<article class="post">
<h2>Title</h2>
<p>Content...</p>
</article>
<script>
// Add edit UI only in edit mode
if (hyperclay.isEditMode()) {
document.querySelectorAll('.post').forEach(post => {
const toolbar = document.createElement('div');
toolbar.innerHTML = '<button>Edit</button>';
toolbar.setAttribute('onbeforesave', 'this.remove()');
post.prepend(toolbar);
});
}
</script>
Form State Management
<form onbeforesave="this.querySelectorAll('input[type=password]').forEach(i => i.value = ''); this.classList.remove('error', 'success')">
<input type="text" name="username" persist>
<input type="password" name="password">
<div class="error" onbeforesave="this.remove()"></div>
</form>
Modal Management
<div class="modal" onbeforesave="this.classList.remove('open'); this.style.display = ''">
Modal content
</div>
Dynamic Content Cleanup
<!-- Clear search results -->
<div class="search-results" onbeforesave="this.innerHTML = ''">
<!-- Populated via JS -->
</div>
<!-- Reset preview -->
<div class="preview" onbeforesave="this.textContent = 'Preview area'">
<!-- Dynamic preview -->
</div>
Advanced Techniques
Version Tracking
<div data-version="1" onbeforesave="if (this.dataset.changed) { this.dataset.version++; delete this.dataset.changed; }">
<p oninput="this.parentElement.dataset.changed = true">
Versioned content
</p>
</div>
Conditional Saving
hyperclay.beforeSave = function(doc) {
const isDraft = doc.body.dataset.status === 'draft';
// Remove elements based on status
doc.querySelectorAll(isDraft ? '[data-published-only]' : '[data-draft-only]')
.forEach(el => el.remove());
};
Batch Operations
<div onbeforesave="this.querySelectorAll('[data-changed]').forEach(el => delete el.dataset.changed)">
<!-- Multiple items with data-changed tracking -->
</div>
Performance Tips
Debounce Expensive Saves
let saveTimeout;
function debouncedSave() {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => hyperclay.savePage(), 2000);
}
// Use for sliders, color pickers, etc.
slider.addEventListener('input', debouncedSave);
Selective Cleanup
hyperclay.beforeSave = function(doc) {
// Only clean specific zones
doc.querySelectorAll('[data-cleanup]').forEach(zone => {
// Targeted cleanup based on zone type
});
};
Debugging Saves
// Monitor what gets saved
const originalBeforeSave = hyperclay.beforeSave;
hyperclay.beforeSave = function(doc) {
console.log('Pre-save:', doc.body.innerHTML.length);
if (originalBeforeSave) originalBeforeSave(doc);
console.log('Post-save:', doc.body.innerHTML.length);
};
Best Practices
- Clean Up Temporary UI - Remove admin controls and error messages
- Preserve User Data - Keep content, remove only UI chrome
- Use onbeforesave Locally - Put cleanup logic near affected elements
- Test Both States - Verify edit and view mode appearance
- Handle Edge Cases - Consider empty states and errors
- Document Complex Logic - Comment non-obvious transformations
Master the save lifecycle to build apps that seamlessly blend editing and viewing!
Last updated on