*restore
Summary
*restore discards staged edits and restores the staged view back to the last stable state of the host data. It is part of the staged editing flow together with *stage and *apply, and it only makes sense when the host is configured with *stage or n-stage.
Alias:
*restoren-restore
Basic example
A typical staged form with apply and restore buttons:
<serc-rod id="profile" data='{
"user": { "name": "Alice", "email": "alice@example.com" }
}'>
<form *stage="draft">
<label>
Name:
<input *input="draft.user.name">
</label>
<label>
Email:
<input *input="draft.user.email">
</label>
<button type="button" *apply>Save</button>
<button type="button" *restore>Reset</button>
</form>
</serc-rod>
Behavior:
- While editing, the view uses a staged copy (
_stage) derived from the host data. *applycopies staged changes into the real data (_data) and remembers that snapshot as the new stable state.*restorediscards the current staged edits and restores the staged view back to the last stable state:- If there has been at least one successful
*apply, it rolls back to that applied snapshot. - Otherwise it rolls back to the original host data.
- If there has been at least one successful
Behavior
Key points:
-
*restoreis a button-like directive:- It attaches a click handler that manipulates the host’s internal staged buffer.
- It does not create or expose new variables in the template scope.
- The attribute value (if any) is ignored;
*restoreis used as a flag only.
-
*restoreonly has an effect when the host Sercrod element has*stageorn-stage:- Without
*stage, the click handler checks the host and does nothing.
- Without
-
Interaction with the staged model:
- The host maintains:
_data: the canonical data._stage: a staged copy used for editing and rendering while staging is active._applied: the last snapshot of_datataken immediately after*apply.
*restorerewrites_stageusing:_appliedif it exists (last applied snapshot), otherwise_data(the current canonical data).
- The host maintains:
-
Rendering impact:
- Since the host’s visible scope is
this._stage ?? this._data, updating_stagefollowed byupdate()immediately reverts the UI to the restored state.
- Since the host’s visible scope is
Relationship to *stage and *apply
*restore is one of three core directives that define the staged editing lifecycle:
-
*stageon the host:- Enables the staged editing model.
- The host constructs
_stageas a clone of the initial scope (usually_dataor a parent-provided scope). - The visible scope becomes
_stagewhenever it is present, falling back to_dataotherwise.
-
*applyon a child element:- Copies the staged content from
_stageinto_datawhen clicked. - After a successful apply:
Object.assign(this._data, this._stage)is performed.- The host re-renders.
_appliedis updated to a clone of the new_datafor use by*restore.
- Copies the staged content from
-
*restoreon a child element:- Uses
_appliedif present, otherwise_data, as the base. - Clones that base into
_stageagain. - Triggers a re-render so the UI reflects the restored state.
- Uses
In other words:
*stage: prepare a safe workspace.*apply: commit workspace changes to canonical data and remember that commit.*restore: discard workspace changes and go back to the last commit (or the original data).
Evaluation timing
*restore is processed during element rendering after host-level *if and other structural checks:
-
If the element with
*restorealso has*iforn-if, the conditional is evaluated first.- If the condition is falsy, the element is not rendered and no restore handler is attached.
- If the condition is truthy, Sercrod proceeds and the
*restorelogic runs.
-
When the renderer encounters
*restore:- It clones the current working element (
work) into a real DOM element (el). - It attaches a single
clicklistener toel. - It appends
elto the parent and stops further processing of that element for the current render.
- It clones the current working element (
-
The
clickhandler runs at user interaction time, not at render time.
Execution model
Conceptually, for each element with *restore or n-restore:
-
During render:
-
Sercrod detects that
workhas*restoreorn-restore. -
It creates
el = work.cloneNode(true). -
It registers:
el.addEventListener("click", handler),
where
handleris closed over the host instance. -
It appends
elto the parent. -
It returns from the directive branch for this element.
-
-
When the user clicks the element:
-
The handler checks if the host has
*stageorn-stage.- If not, it returns without changes.
-
It calculates the base:
base = this._applied ?? this._data.
-
It clones
baseinto_stage:- Preferentially via
structuredClone. - Falls back to
JSON.parse(JSON.stringify(base))if needed.
- Preferentially via
-
It calls
this.update()to re-render the host.
-
-
On re-render:
- Because
_stageis now a fresh clone of the base state, the entire staged view is reset to that state.
- Because
Variable creation and scope layering
*restore does not introduce or modify template-level variables:
- No new local variables are created for each element.
- It does not change the scopes used by expressions inside the template directly.
- Instead, it modifies the internal data buffers (
_stageand indirectly the visible scope) of the host component.
From the perspective of expressions:
- After a successful restore, any expression that reads from the visible scope (for example
user.name) will see values from the restored staged buffer. - Special helpers like
$data,$root, and$parentare still provided in the usual way by Sercrod’s expression engine.
Parent access
*restore does not provide a dedicated handle to parent scopes on its own:
- It acts on the enclosing Sercrod host where
*stageis configured. - The handler uses internal properties (
this._stage,this._data,this._applied) of that host. - Templates can still rely on
$rootand$parentas usual when rendering, but those bindings are independent of*restoreitself.
The main effect of *restore is to redefine the contents of _stage, which then becomes the source for subsequent renders.
Use with conditionals and loops
You can combine *restore with conditionals and loops on the same or surrounding elements, as long as they do not conflict structurally:
-
On the same element:
-
*ifand*restoreon a button:<button type="button" *if="isDirty" *restore> Reset changes </button>- The button only appears when
isDirtyis truthy. - When visible and clicked, it restores the staged data.
- The button only appears when
-
-
Inside loops:
-
You can put
*restorein a repeated area if it logically refers to the same staged host:<serc-rod data='{"rows":[{"id":1},{"id":2}]}' *stage="draft"> <table> <tbody *each="row of draft.rows"> <tr> <td *print="row.id"></td> <td> <button type="button" *restore>Reset table edits</button> </td> </tr> </tbody> </table> </serc-rod>- All restore buttons operate on the same staged host.
- Clicking any of them resets the entire staged view, not just a single row.
-
Best practices
-
Use
*restoreonly when*stageis enabled:- Without
*stageorn-stageon the host,*restoredoes not perform any meaningful work. - Treat
*restoreas part of a staged editing pattern, not as a global reset.
- Without
-
Pair
*restorewith*apply:-
*restorerelies on_appliedfor “last stable state”. -
Without
*apply, it falls back to_data, which can still be useful as an initial reset. -
In a typical workflow, you will have:
- One or more staged inputs bound into the staged scope.
- A
Savebutton with*apply. - A
Resetbutton with*restore.
-
-
Avoid mixing unrelated behaviors on the same button:
- It is technically possible to add other attributes such as
@clickalongside*restore, but that will combine event handlers. - For clarity and maintainability, keep
*restorebuttons focused on restore behavior and use separate buttons for additional actions.
- It is technically possible to add other attributes such as
-
Think in terms of “last committed change”:
*restoredoes not reconstruct an arbitrary history; it only knows the last successfully applied snapshot.- If you need multi-step undo or history, you should build that at the data layer and expose it to Sercrod as part of
_dataor_stage.
Additional examples
Simple “revert to original data” without any prior apply:
<serc-rod id="simple" data='{"counter": 0}' *stage="draft">
<p>Value: <span *print="draft.counter"></span></p>
<button type="button" *restore>Reset</button>
<button type="button" @click="draft.counter++">Increment</button>
</serc-rod>
- On first render:
_stageis cloned from_data({ counter: 0 }).- The view shows
0.
- Clicking
Incrementchangesdraft.counterinside_stage. - Clicking
Resetclones_datainto_stageagain (since_appliedis unset), bringing the view back to0.
Staged form with multiple commits:
<serc-rod id="multi" data='{"value": "initial"}' *stage="draft">
<input *input="draft.value">
<button type="button" *apply>Apply</button>
<button type="button" *restore>Restore</button>
</serc-rod>
- After editing the input and clicking
Apply:_data.valuebecomes the edited value._appliedis updated to the new_data.
- Further edits only affect
_stage. - Clicking
Restorediscards those new edits and clones_appliedinto_stage, returning the UI to the last applied value.
Notes
*restoreandn-restoreare aliases; choose one style per project and stick to it for consistency.- The directive does not use its attribute value; any text written in
*restore="..."is ignored. *restoreis a staged-data helper only:- It assumes the host is in staged mode via
*stageorn-stage. - It rebuilds the staged buffer from
_appliedor_dataand re-renders.
- It assumes the host is in staged mode via
- Because it operates on
_stage,*restoreaffects the visible state immediately, but it does not directly overwrite_data._datachanges only when*applyor other data-mutating mechanisms are invoked.
- There are no additional hard restrictions specific to
*restorebeyond its dependency on*stage:- It does not conflict with
*include,*import, or other structural directives the way*eachdoes, because it does not reshape the host’s children.
- It does not conflict with