*save
Summary
*save.file exports the host data (or its staged view) as a JSON file in the browser. *save.session stores the same JSON payload in browser sessionStorage. *save.store stores the same JSON payload in persistent browser storage backed by IndexedDB. It is typically used on a button inside a <serc-rod> host. When clicked, Sercrod collects the host’s current data and builds a JSON string. File saves start a download; session saves write that string under the storage key given by *save.session; store saves write it under the key given by *save.store.
By default, *save exports the entire host data. Use *keys to export only selected top-level properties. *save remains as the legacy-compatible file form, equivalent to *save.file when no storage suffix is used.
Basic example
Save the entire host data:
<serc-rod id="profile" data='{"name":"Alice","age":30}'>
<button *save.file>Download profile JSON</button>
</serc-rod>
Behavior:
- The
<button>is cloned and given a click handler by Sercrod. - When the button is clicked, Sercrod takes the host’s current data and serializes it to JSON.
- A file named like
Sercrod-YYYYMMDD-HHMMSS.jsonis generated and downloaded by the browser.
Save selected keys to an explicit filename:
<button type="button" *save.file="'profile-backup.json'" *keys="profile settings">
Save profile
</button>
In this form:
*save.filedescribes the file destination and optional filename.*keysdescribes which host data keys are saved.
Save selected keys into the current browser session:
<button type="button" *save.session="'profile-draft'" *keys="profile settings">
Save session draft
</button>
In this form:
*save.sessiondescribes thesessionStoragekey.*keysselects which host data keys are serialized.- Omitting
*keyssaves the whole host data or stage.
Save selected keys into persistent browser storage:
<button type="button" *save.store="'profile-draft'" *keys="profile settings">
Save persistent draft
</button>
In this form:
*save.storedescribes a persistent browser storage key.- Sercrod stores a JSON string in IndexedDB.
- The value remains available after page reloads and browser restarts, subject to normal browser storage policy.
Behavior
-
*save.fileattaches a click handler to the element it is placed on. -
*save.sessionattaches the same kind of click handler, but writes JSON towindow.sessionStorage. -
*save.storeattaches the same kind of click handler, but writes JSON to IndexedDB. -
The handler runs in the context of the surrounding Sercrod host and serializes:
this._stageif it exists, otherwisethis._data.
-
No network request is sent by
*saveitself. -
In browsers with
showSaveFilePicker(), Sercrod first opens the native save picker and uses the*save.filefilename assuggestedName. The user may change the name or location. If the API is unavailable or fails before a choice is made, Sercrod uses the portable<a download>path. -
The portable fallback writes the JSON into a Blob and clicks a temporary
<a download>element. The browser then handles the download with the requested filename. -
Canceling the save picker cancels the save; it does not trigger a fallback download.
-
For
*save.session, the resulting JSON is stored withsessionStorage.setItem(storageKey, json). -
For
*save.store, the resulting JSON is stored in thesercrod-storeIndexedDB database by default. -
After a successful save action, a
CustomEvent("sercrod-saved")is dispatched from the host for application-specific hooks.
Aliases and compatibility:
*save.fileandn-save.fileare aliases.*save.sessionandn-save.sessionare aliases.*save.storeandn-save.storeare aliases.*saveandn-saveremain supported as the old file-save spelling.- In new examples, prefer explicit action forms such as
*save.file,*save.session, or*save.storeplus*keys.
Data source and property selection
Data source:
-
*savealways uses host-level data, not per-element scope variables. -
On click:
- If the host has a staged buffer (
_stage),*savereads from_stage. - Otherwise, it reads from the committed data object
_data.
- If the host has a staged buffer (
Key selection:
-
Without an attribute value:
*save.fileexports the entire data object (_stageor_data).*save.sessionand*save.storerequire a storage key value, but still save the entire data object when*keysis omitted.
-
With
*keys:- The
*keysvalue is treated as a whitespace-separated list of top-level property names. - These names are not expressions; they are taken as-is and are not evaluated.
- Only properties that exist on the data object are copied into a new object.
- The
Example (selective save):
<serc-rod id="settings" data='{
"user": { "name": "Alice", "age": 30 },
"theme": { "mode": "dark" },
"debug": true
}'>
<!-- Only save "user" and "theme" from the host data -->
<button *save.file="'user-theme.json'" *keys="user theme">Download user+theme</button>
</serc-rod>
In this example:
-
srcishost._stage ?? host._data. -
If
*keysis"user theme", Sercrod builds:data = { user: src.user, theme: src.theme }(if those properties exist).
-
The JSON file contains only
userandthemeat the top level. -
Nested paths (such as
user.name) are not supported by*keysdirectly.
Old spelling:
<button *save="user theme">Download user+theme</button>
This remains supported for compatibility. Treat the value as old *keys syntax, not as a filename.
Evaluation timing
Render-time:
-
When Sercrod renders the host, it looks for elements with
*save,*save.file,*save.session,*save.store, or theirn-aliases. -
For each such element:
- Sercrod clones the element.
- Attaches a click handler on the clone.
- Appends the clone to the parent.
- Returns from the element renderer without recursing into the children of that clone.
Click-time:
-
When the user clicks the save button:
-
Sercrod resolves the action attribute.
*save.filegives an optional filename.*save.sessiongives asessionStoragekey.*save.storegives a persistent browser storage key.- legacy
*savevalues are treated as old key-selection syntax.
-
Sercrod selects
src = this._stage ?? this._datafrom the host. -
It builds a plain object:
- Entire
srcif no*keyslist was provided. - A subset object if
*keyswas provided.
- Entire
-
It serializes that object with
JSON.stringify(data, null, 2). -
It writes the JSON to the selected destination: file download,
sessionStorage, or IndexedDB-backed persistent browser storage. -
It dispatches the
sercrod-savedevent from the host.
-
Because the JSON is built at click time, *save always reflects the current state of _stage or _data at the moment of the click.
Execution model
Internally, *save behaves as follows (conceptually):
-
During render, Sercrod finds an element
workwith a save directive. -
Sercrod clones
workintoel. -
Sercrod attaches:
el.addEventListener("click", () => { /* build JSON and save to the selected destination */ }).
-
Sercrod appends
elto the parent node and returns, without processingel’s children for further Sercrod directives.
On click, the handler:
-
Resolves the action attribute and
*keys. -
Selects
src:src = host._stage ?? host._data.
-
Builds
data:- If there is a
*keyslist,datais a new object populated only with properties present insrc. - Otherwise,
dataissrcitself.
- If there is a
-
Serializes
datawith a pretty-printedJSON.stringify(data, null, 2). -
If the action is
*save.store, writes{ key, json, updatedAt }into the configured IndexedDB object store and returns. -
If the action is
*save.session, writes the JSON string withsessionStorage.setItem(storageKey, json)and returns. -
Otherwise, creates a Blob of type
application/json. -
Creates an
ObjectURLand a temporary<a>element with:href = url.download = "Sercrod-YYYYMMDD-HHMMSS.json"(in the local time of the browser).
-
Programmatically clicks the anchor to prompt download.
-
Cleans up (removes the anchor from the DOM and revokes the
ObjectURL). -
Dispatches
CustomEvent("sercrod-saved", { detail: { ... } })from the host.
Use on nested elements and scope
*savemust live inside a Sercrod host to be meaningful, since it reads from the host’s_stageor_data.*savedoes not use per-element scope; it only uses the host’s data object.- Placing
*saveon a deeply nested element is allowed, but it still always saves the surrounding host’s data, not a subset scoped by*foror*each.
In other words:
- The location of the
*savebutton in the DOM tree does not change the data source. - It only changes where the button appears in the layout.
Events
After a successful save, Sercrod dispatches a bubbling, composed CustomEvent from the host:
-
Event type:
"sercrod-saved"
-
Event detail structure:
detail.stage:"save"for file/legacy saves,"save.session"for session saves, or"save.store"for store saves.detail.host: the Sercrod host element (<serc-rod>instance).detail.fileName: the file name used for file downloads (for example"Sercrod-20251205-093000.json").detail.storage:"session"or"store"for non-file saves.detail.storageKey: the storage key for*save.sessionor*save.store.detail.props: the property list array if provided;nullif no list was specified.detail.keys: the same property list array, provided for the unified action syntax.detail.json: the JSON string that was generated.
Example hook:
document.addEventListener("sercrod-saved", (evt) => {
const { host, fileName, storage, storageKey, props, json } = evt.detail;
console.log("Saved from host:", host.id);
console.log("File name:", fileName);
console.log("Storage:", storage, storageKey);
console.log("Props:", props);
console.log("JSON preview:", json.slice(0, 200));
});
You can use this event to:
- Mirror the saved JSON to another storage or API when the built-in file/session/store destinations are not enough.
- Show a toast notification after the download is triggered.
- Log or audit save operations.
Best practices
-
Treat
*save.fileelements as simple buttons:- Because the renderer does not recursively process children of
*savehosts after cloning, avoid placing other Sercrod directives inside the same element. - Use plain text or static markup inside the button where possible.
- Because the renderer does not recursively process children of
-
Use
*keysfor focused exports:-
If your host data is large, consider exposing smaller subsets via:
*save.file="'profile.json'" *keys="profile settings"*save.file="'chart.json'" *keys="chart filters"
-
-
Keep the root data export-friendly:
- Plan your top-level keys (
user,settings,rows,config, and so on) so that it is easy to export meaningful subsets by name.
- Plan your top-level keys (
-
Combine with matching
*loadforms for round trips:- Use
*save.fileand*load.filefor user-visible JSON files. - Use
*save.sessionand*load.sessionfor session-scoped browser storage. - Use
*save.storeand*load.storefor persistent browser storage.
- Use
-
Use
sercrod-savedfor integration:- Attach listeners to
"sercrod-saved"if you want to route the JSON elsewhere instead of or in addition to the download.
- Attach listeners to
Advanced - Using save forms with *stage, *apply, *restore, load forms, and *post
*save is part of a broader data management workflow:
-
*stage:- Enables a staged buffer
_stagefor the host (a working copy of the data). - When
_stageexists,*saveprefers_stageover_data. - This lets you export the staged view without committing it.
- Enables a staged buffer
-
*apply:- Copies
_stageinto_dataand updates the host. - Subsequent
*saveclicks, after*apply, will see the committed state in_data.
- Copies
-
*restore:- Rolls back
_stageto the last snapshot, or to_dataif no snapshot is available. - After a restore,
*saveagain sees whatever_stagecurrently holds.
- Rolls back
-
*load:- Reads JSON from a file, session storage, or persistent browser storage and merges it into
_stageor_data. - You can use matching
*loadforms to import JSON previously written by*saveforms.
- Reads JSON from a file, session storage, or persistent browser storage and merges it into
-
*post:- Sends host data to a server as JSON over HTTP.
*saveis complementary to*post: one saves locally as a file, the other sends over the network.
The core rule is:
*savealways targets “the current data view” of the host, prioritizing_stagewhen present.- This makes it safe to stage edits with
*stage, try them out, export via*save, and later apply or restore as needed.
Notes
*save.fileandn-save.fileare aliases.*save.sessionandn-save.sessionare aliases.*save.storeandn-save.storeare aliases.*saveandn-saveremain old compatible file-save spellings.- In old syntax, the value of
*saveis parsed as plain text and split by whitespace as a key list. - When no property list is provided, the entire
_stage ?? _dataobject is serialized. - When a property list is provided, only the listed top-level properties are included if they exist.
- The file name is generated as
"Sercrod-YYYYMMDD-HHMMSS.json"using the browser’s local time. *saveitself does not change_stageor_data; it is a read-only export operation.- There are no special structural restrictions specific to
*savebeyond the general behavior described above; it can be combined with directives such as*ifon the same element, as long as you keep in mind that*saveturns that element into a “save button” whose children are not further processed by Sercrod.