*fetch
Summary
*fetch loads JSON from a URL and writes it into Sercrod host data. The JSON response can either replace the entire root data object or be merged into a specific property path. *fetch has an alias n-fetch.
On a Sercrod host element (<serc-rod>), *fetch is typically used for initial data loading. On normal elements (such as <button> or <div>), it can be used for explicit reload buttons or one-time auto fetches.
Key points:
- URL spec is a plain string
URL[:prop](no expression evaluation). propis optional. If omitted, the entire data root is replaced by the fetched JSON.- If
propis present, that path insidedatais updated instead. - For non-clickable elements,
*fetchauto-runs once per URL (with a special rule forts=query parameters). - For clickable elements,
*fetchruns on click only. - On the host
<serc-rod>,*fetchruns duringconnectedCallbackbefore the first render.
Basic example
Initial data load into a host:
<serc-rod id="app" *fetch="/api/items.json:items">
<h1>Items</h1>
<ul *each="item of items">
<li *print="item.name"></li>
</ul>
</serc-rod>
Behavior:
- When the Sercrod host is connected, it calls
fetch("/api/items.json"). - The JSON response is written into
data.items. - After the fetch completes, Sercrod performs the first render and the list shows the loaded items.
URL spec format
The *fetch attribute value is interpreted as a simple string in the following format:
URLURL:propURL:base[key]
Parsing rules:
-
The runtime splits the spec with
spec.split(":")and uses:file: the first part before the first:.prop: the second part after the first:(if any).
-
fileis passed directly tofetch(file). -
propcontrols where the JSON is stored:-
No
prop: the entire rootdatais replaced with the JSON. -
propwithout brackets:- Example:
"/api/items.json:items"
The JSON response becomesdata.items.
- Example:
-
propwith brackets:-
Example:
"/api/users.json:users[current]"
The JSON response becomesdata.users[current]. -
Internally, the runtime uses a simple pattern:
- It matches
(.+?)\[(.+)\]to splitbaseandkey. - It ensures
data[base]is an object and writesdata[base][key] = json.
- It matches
-
-
Important implications:
- Only the first
:in the spec is treated as the separator betweenfileandprop. - If your URL itself contains
:, the part before the first:is used asfileand the rest is treated asprop. In practice, you should use relative paths or encoded URLs to avoid ambiguous:characters.
Behavior
Core behavior:
-
*fetchalways performs an HTTP GET using the globalfetch()API:fetch(file).then(r => r.json())
-
The response is expected to be valid JSON. If
r.json()fails, the error is routed to an error event (see “Events” below).
Data writing rules:
-
When
propis present:-
If
propmatchesbase[key]:- Ensure
data[base]exists and is an object. - Write
data[base][key] = json.
- Ensure
-
Otherwise:
- Write
data[prop] = json.
- Write
-
-
When
propis absent:- The entire root data object is replaced.
- The new root is wrapped with Sercrod’s internal proxy wrapper to keep observation consistent.
Conceptually:
data = jsonbecomesdata = wrap(json).
Update:
- After a successful fetch, Sercrod schedules
update()on the host usingrequestAnimationFrame. - The template is re-rendered against the updated data.
Errors:
- On fetch or JSON parse failure, Sercrod does not modify data.
- Instead, it emits a
sercrod-load-errorevent (see “Events”). update()is not automatically called by*fetchin the error path.
Host vs normal elements
*fetch behaves differently depending on whether it is placed on the Sercrod host or on a normal element.
-
On the Sercrod host (
<serc-rod>):-
Handled inside
connectedCallback. -
Sequence:
-
The host parses
data. -
If the parent host is currently loading (for example from its own
*fetch), the child host waits. -
If the host has
*fetchorn-fetch:this._loadingis set totrue.- The spec is read from the attribute.
- Warnings are temporarily suppressed for the duration of
_do_load. _do_load(spec)is called.
-
On success:
- Data is updated according to the rules above.
this._loadingis set tofalse.requestAnimationFrame(() => this.update())is called.
-
If the host does not have
*fetch:- The host calls
update()normally.
- The host calls
-
Implications:
- When
*fetchis on the host, the first Sercrod render happens after the fetch completes. - Child hosts can check
parent._loadingand delay their own initialization until the parent is done.
-
-
On normal elements (button, link, div, etc.):
- Handled inside the element rendering pipeline.
- Sercrod clones the element, sets up
*fetchbehavior, appends the clone, and renders its children.
Clickable vs non-clickable:
-
Clickable elements:
-
Recognized as clickable if:
tagNameisBUTTON, ortagNameisAwithoutdownload, ortagNameisINPUTwithtypeinbutton,submit,reset,image.
-
For clickable elements:
- No automatic fetch is performed.
- A click listener is attached, calling
_do_load(spec)on each click.
-
-
Non-clickable elements:
-
For non-clickable tags (such as
div,section,span, etc.):-
*fetchis auto-triggered once after initial render. -
A “once key” is derived from the spec to prevent repeated automatic fetches.
-
If this once key has not been seen before, Sercrod:
- Records it in an internal set for this host.
- Schedules
_do_load(spec)withrequestAnimationFrame.
-
-
Children:
- In both cases, after cloning and setting up
*fetch, Sercrod renders the element’s children normally. - Child directives (
*print,*if,*each, etc.) can immediately read from existing data. - After the fetch completes, the host rerender updates those children with the new data.
Once-key and the ts parameter
For non-clickable elements, *fetch auto-runs “at most once per URL spec” by using an internal once key.
The once key is derived as follows:
-
Sercrod tries to parse the spec as a URL relative to
location.href. -
If that succeeds:
- It removes the
tsquery parameter from the URL. - It uses
pathnameplus the remaining query string to form the once key.
- It removes the
-
If parsing fails:
- It removes any
tsparameter with a regular expression. - It also strips trailing
?or&characters.
- It removes any
Implications:
-
Adding a
tsquery parameter is treated as a cache-busting trick that does not affect the once key. -
Re-rendering with the same
*fetchspec will not auto-run the fetch again for non-clickable elements. -
To re-run fetch automatically, you must either:
- Change the spec in a way that changes the once key, or
- Use a clickable element and let the user trigger fetch explicitly.
Events
*fetch communicates its progress via DOM events on the host:
-
Before fetch starts:
-
sercrod-load-start-
detail:stage:"fetch"host: the Sercrod host instancespec: the full spec stringfile: the URL part (file)prop: the property spec (if any)
-
-
-
After a successful fetch, before
update():-
sercrod-loaded-
detail:-
stage:"fetch" -
host: the Sercrod host instance -
spec: the full spec string -
file: the URL part -
prop: the property spec (if any) -
json: the parsed JSON value -
paths: an array of logical data paths that were touched- Either
["$root"]when the root was replaced, or - A list such as
["items"]or["users[current]"]when merging into a property.
- Either
-
-
-
-
On error:
-
sercrod-load-error-
detail:stage:"fetch"host: the Sercrod host instancespec: the full spec stringfile: the URL partprop: the property spec (if any)error: the error message string
-
-
Notes:
- These events bubble and are composed.
- They allow external code to observe loading, success, and failure, and to implement custom UI such as progress indicators or retry buttons.
*fetch does not set or use $pending, $error, $download, or $upload in data. Those state helpers are used by *api and *post, not by *fetch.
Interaction with *api, *post, and *into
*fetch, *post, and *api are all “request” directives that write into host data, but they differ in capabilities:
-
*fetch:- Simple GET-only helper.
- Takes a static
URL[:prop]string. - Writes JSON into data or a property path.
- Does not use
*intoor$pending/$error/$download/$upload.
-
*post:- Sends JSON built from host data via POST.
- Uses a similar
URL[:prop]spec to map the JSON response back into data. - Integrates with
$pending,$error,$upload,$download.
-
*api:- General-purpose API helper.
- Supports method, headers, file uploads,
*into, and once rules at the API level.
Ordering and combinations:
-
Inside the element rendering pipeline:
*postis processed before*fetch.*fetchis processed before*api.
-
Each of these directives claims the element and returns early after setting up behavior, so only the first matching directive on a given element executes.
Recommendations:
- Do not combine
*fetchwith*postor*apion the same element. Only the first one in the internal order will take effect, and the rest will be ignored. - Use separate elements if you need multiple request types (for example, a
*fetchfor initial data and a*postbutton for submitting changes).
*into is reserved for *api and is not used by *fetch.
Use with conditionals and loops
*fetch is not a structural directive by itself, so it composes with structural directives as long as they are applied at different levels.
Typical patterns:
-
Conditional host fetch:
- For a host,
*fetchruns fromconnectedCallback. You normally do not wrap the host itself with*if.
- For a host,
-
Conditional child fetch:
<serc-rod id="app" data='{"ready": false, "items": []}'> <button *if="ready" *fetch="/api/items.json:items" > Load items </button> </serc-rod>*ifis processed before*fetch.- If
readyis false, the button (and its*fetch) is not rendered.
-
Fetch inside loops:
-
You can place
*fetchon elements inside*foror*eachbodies, but you should be cautious:- Each rendered element with
*fetchmay set up its own auto fetch or click handler. - Non-clickable
*fetchinside a loop can trigger many automatic requests, once per iteration.
- Each rendered element with
<ul *each="user of users"> <li> <span *print="user.name"></span> <button *fetch="'/api/user/' + user.id + '.json:details'"> Load details </button> </li> </ul>- Note that the spec for
*fetchis still a literal string in the current implementation; the example above illustrates intent, but the runtime does not evaluate expressions in the spec. For dynamic URLs, prefer*apiwhich expands text with%expr%placeholders.
-
Server-side contract and recommended API shape
Because *post, *fetch, and *api all treat HTTP communication as “JSON in, JSON out” and share the same state flags, it is natural to standardize server-side handlers around this contract.
Recommended approach on the server:
-
Treat Sercrod-driven endpoints as JSON endpoints:
- Always accept a JSON request body for write operations.
- Always return a JSON response for both success and application-level errors.
- Use a stable envelope shape so that
URL[:prop]and*intocan be wired consistently.
-
Reuse the same processing pipeline:
- Parse JSON.
- Run validation, authentication, business logic, and logging in a shared middleware.
- Produce a JSON object that Sercrod can store as-is into
data[prop],data[base][key], or a target selected by*into.
Benefits for server-side code:
- You can implement a “Sercrod API style” once and reuse it across multiple endpoints.
- Monitoring and logging become easier because every Sercrod request and response has the same structure.
- Frontend and backend teams can agree on a single JSON contract instead of negotiating many small variations.
Position in Sercrod’s design:
- Sercrod does not force this server-side style, but the runtime is optimized around it:
*postand*fetchshare theURL[:prop]rule and write values back without further transformation.*apiwrites the raw response into the variable named by*into.- All of them update
$pending,$error,$download, and$uploadin a consistent way.
- For new projects that adopt Sercrod end to end, designing server APIs to follow this unified JSON contract is strongly recommended.
- For existing APIs, you can:
- Use
*apito integrate with legacy endpoints as they are. - Gradually introduce Sercrod-style JSON endpoints for new features and move existing endpoints toward the same contract when possible.
- Use
Best practices
-
Use
*fetchfor simple JSON GETs:- When you only need to GET JSON and place it in a single property or replace the root,
*fetchis the smallest tool.
- When you only need to GET JSON and place it in a single property or replace the root,
-
Keep the spec simple and relative:
- Prefer relative URLs such as
"/api/items.json"and use:propfor data placement. - Avoid unencoded
:in the URL part, since*fetchusesspec.split(":").
- Prefer relative URLs such as
-
Use props for partial updates:
- Use
:propwhen you want to update a subset of data without replacing the root. - Use
base[key]syntaxes such as:users[current]when you need keyed slots.
- Use
-
Reserve
*fetchon the host for initial data load:- Host-level
*fetchis ideal for “page load → fetch → render once” behavior. - It keeps the initial render in sync with fetched data.
- Host-level
-
Use clickable
*fetchfor explicit reloads:- Putting
*fetchon a button or link gives users control over when to refresh data. - This avoids the implicit “auto-run once” behavior of non-clickable
*fetch.
- Putting
-
Handle errors through events:
- Attach listeners for
sercrod-load-errorwhen you need explicit error handling. - For more advanced error-state tracking inside data, consider using
*apior*postinstead.
- Attach listeners for
Additional examples
Host-level full replacement:
<serc-rod id="profile" *fetch="/api/profile.json">
<h1 *print="name"></h1>
<p *print="email"></p>
</serc-rod>
- The entire root data is replaced by the JSON from
/api/profile.json. - The template then uses
nameandemailfrom the new root.
Host-level partial update:
<serc-rod id="dashboard" data='{"stats": {}, "user": {}}' *fetch="/api/stats.json:stats">
<h2>Dashboard</h2>
<section>
<p>Total users: <span *print="stats.totalUsers"></span></p>
<p>Active users: <span *print="stats.activeUsers"></span></p>
</section>
</serc-rod>
- Only
data.statsis overwritten by the JSON response. - Other properties such as
data.userremain untouched.
Non-clickable auto fetch:
<serc-rod id="news" data='{"articles": []}'>
<section *fetch="/api/news.json:articles">
<h2>Latest news</h2>
<ul *each="article of articles">
<li>
<strong *print="article.title"></strong>
<p *print="article.summary"></p>
</li>
</ul>
</section>
</serc-rod>
- The
<section>is not clickable, so*fetchauto-runs once per spec. - After the fetch,
articlesis updated and the list is rendered.
Clickable reload button:
<serc-rod id="log-viewer" data='{"log": []}'>
<button *fetch="/api/log.json:log">
Reload log
</button>
<ul *each="entry of log">
<li *print="entry.message"></li>
</ul>
</serc-rod>
- Each click triggers a fresh GET request for
/api/log.json. - The JSON response replaces
data.log, and the list is updated.
Notes
*fetchandn-fetchare aliases with identical behavior.- The spec is treated as a raw string;
*fetchdoes not evaluate expressions or expand%expr%placeholders. - The directive expects JSON responses. Non-JSON responses cause
r.json()to reject and result insercrod-load-error. - Root replacement via
*fetchalways re-wraps the new root data to maintain Sercrod’s internal observation and proxy invariants. - When used on normal elements,
*fetchsets up behavior on a cloned element and then returns early; no additional directives on the same element are evaluated after*fetchin the rendering pipeline. - On hosts, only one
*fetchper host is meaningful; additional fetches should be implemented via nested elements or separate buttons. - For complex API use cases (headers, methods, payloads, file uploads, and rich state tracking), consider using
*apiand*postinstead of*fetch.