Method Elements
A method element is an HTML element whose tag is a Schema-defined method name in a bound XML namespace. At composition time the element is replaced with the result of evaluating the method's implementation. The implementation is written in Sessel or in JavaScript (a JavaScript/Module); both produce the same kinds of result (see How the result becomes HTML) and differ only in how they receive self/this and their arguments.
When to use
Reach for a method element when the same fragment of HTML is produced by Sessel logic you'd rather declare once on a Schema than repeat inline in every page that needs it. Common shapes:
- A list/menu/card whose contents come from a search query.
- A computed snippet that depends on the current request, host, or session.
- A polymorphic widget that branches on the data it's given via attributes.
If the snippet is purely declarative (a fragment of another document with no logic), use Includes instead. If it's a Liquid template parameterised by a Resource Binding, use Templating.
Element form
<html xmlns:t="urn:Test">
<body>
<t:foo></t:foo>
</body>
</html>
Two ingredients:
- An xmlns declaration binding a prefix (
t) to a schema URL (urn:Test). - An element whose tag is
prefix:method-name(t:foo).
The xmlns declaration may live on <html>, <body>, or any ancestor of the element you want to dispatch. The binding is scoped to its declaring element's subtree.
Attribute form
A method can also be dispatched from a prefix:name="value" attribute on an ordinary (non-prefixed) element, rather than from a prefixed tag:
<html xmlns:t="urn:Test">
<body>
<div t:foo="bar"></div>
</body>
</html>
The same two ingredients apply — an xmlns declaration binding prefix to a schema, and name matching a declared method on that schema — but the dispatch target is the attribute, not the element's tag. Differences from the element form:
- Argument passing. Only one value can be passed this way: the attribute's value is bound to the method's first declared parameter. (The element form matches each attribute name against a declared parameter name and can pass several; see Passing arguments.)
- Effect on the host element. If the method's
returnstype ishttps://pagelove.org/Elementand the result is notNull, the whole host element (not just the dispatching attribute) is replaced by the result, using the same result table as the element form. Otherwise the dispatching attribute is removed from the output and the host element and its content are otherwise unchanged; anyContextmutation the method made during dispatch is still applied, but is visible only to the host element and its descendants (later siblings, and anything above the host element, never see it) — the same subtree scoping described for the element form under JavaScript implementations, which applies equally to a Sessel implementation'sContextmutations. doesNotUnderstandparameter shape differs from the element form — seedoesNotUnderstandfallback.- A
prefix:transientattribute is never dispatched, even whenprefixis a bound namespace with adoesNotUnderstandmethod —transientis reserved as a marker consumed by transient element resolution, not a method name.
Multiple prefix:name="value" attributes on the same element (whether from the same or different bound prefixes) are not dispatched in the order they're written. Any *:template attribute is always dispatched last, after every other prefixed attribute on the element, regardless of where it appears in the markup — this lets p:template read Context bindings that other attributes on the same element just wrote. Among the rest, dispatch is one after another — unless one of them replaces the host element. As soon as a dispatched method whose declared returns type is https://pagelove.org/Element yields a non-Null result — triggering the whole-host-element replacement described above, regardless of what kind of value the result actually is — dispatch of that element's remaining attributes stops: any attribute still waiting its turn never runs, including a side-effect-only one (a Context mutation with no element return), and this applies to *:template too if it hasn't run yet. If you need every attribute's side effects to run, don't combine a replacing method with other prefixed attributes on the same element.
This attribute-form dispatch is also how the built-in Resource Binding (r:), Expression Binding (e:), and JavaScript expression binding (j:) namespaces work under the hood: each declares a schema whose doesNotUnderstand method is dispatched through this same generic path, with the attribute value arriving as parameters[0] (see doesNotUnderstand fallback). Those pages describe the binding-specific behaviour; this page describes the general dispatch mechanism they specialize.
Defining the method
A method element requires a modeled schema — it is the composition feature a schema's Methods unlock. (Composition mechanisms that need no schema — templates, resource and expression bindings, includes — are listed under Composing pages.)
The schema bound to the prefix must declare a Method whose name matches the element's local name, and must be loaded into the host's cache (registered when the document containing the schema is PUT/POST'd; until then dispatch fails — see Error cases). In brief, a Method is a <li itemprop="property" itemscope itemtype="https://pagelove.org/Method"> carrying a name, an implementation (Sessel or JavaScript), an optional returns type, and optional parameter items. See Methods for the full declaration reference; the example below shows one inline, and JavaScript implementations covers the JavaScript calling convention.
How the result becomes HTML
The implementation is evaluated with the dispatched element bound as self (Sessel) or this (JavaScript). The returned value determines how the document tree is updated, the same way for both languages:
| Return type | Result |
|---|---|
Element (a constructed element such as new ul { ... }) |
Serialised as HTML, parsed as a fragment, spliced in place of the method element. Composition recurses into the spliced fragment. |
Instance of a Schema |
Serialised as the instance's microdata, spliced in place. Composition recurses. |
List of Element/Instance |
Each item serialised, all spliced in document order. |
Scalar (String, Integer, Boolean, etc.) |
Stringified via the standard value_to_html rules and inserted as text. |
Null |
Element is removed from the tree. |
JavaScript implementations
An implementation whose itemtype is https://pagelove.org/JavaScript/Module carries an ES module in its source. The module's default export is the method:
<div itemprop="implementation" itemscope itemtype="https://pagelove.org/JavaScript/Module">
<script itemprop="source" type="module">
export default () => {
const d = new DOMParser().parseFromString("<p>hello</p>", "text/html");
return d.querySelector("p");
};
</script>
</div>
When such a method is dispatched in the composition pipeline:
thisis the dispatched element (the host element), as a microdata object.- Parameters are passed positionally, in the order they are declared on the Method, with values taken from the matching attributes on the element (an absent attribute is
null). This is the same calling convention as an in-VM call:function (a, b) { … }receives theaandbattribute values in order. (Sessel implementations instead receive their parameters as named local variables — see Passing arguments.) - A read-only
documentglobal is available, scoped to the dispatched element's subtree. It supports queries (document.querySelector(…)); mutations through it throwNoModificationAllowedError. A method that builds a fragment to return constructs it in a freshnew DOMParser().parseFromString(…)document. - The return value is handled by the table in How the result becomes HTML. An element node, a
NodeList, or an array of element nodes is spliced; a string/number/boolean is inserted as text;null/undefinedremoves the element. - The method may mutate
Context(Context.foo = "bar"). Mutations are visible to the dispatched element and its descendants only — a nested binding or template inside the same subtree reads the new value, but sibling and ancestor elements never see it. Request data is read throughContext.request.
The source may be an async function; the pipeline drives its returned promise to settlement.
For declaring JavaScript bindings inside Schema HTML in general (defaults, @read, @computed, methods), see Schema definitions in HTML.
JavaScript example
A method whose JavaScript implementation builds and returns a <p>. As with the Sessel example below, the schema is inline so a single PUT registers it.
Suppose /js-method-element-demo.html contains
PUT /js-method-element-demo.html HTTP/2
Host: 127.0.0.1
Content-Type: text/html
<!DOCTYPE html>
<html xmlns:j="urn:JsDemo">
<body>
<main>
<j:hello></j:hello>
</main>
<div hidden itemscope itemtype="https://pagelove.org/AuthorizationRule">
<meta itemprop="actor" content="*">
<meta itemprop="resource" content="/*">
<meta itemprop="method" content="GET">
<meta itemprop="action" content="allow">
</div>
<div hidden itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="urn:JsDemo">
<ul>
<li itemprop="property" itemscope itemtype="https://pagelove.org/Method">
<meta itemprop="name" content="hello">
<meta itemprop="returns" content="https://pagelove.org/Element">
<div itemprop="implementation" itemscope itemtype="https://pagelove.org/JavaScript/Module">
<script itemprop="source" type="module">export default () => { const d = new DOMParser().parseFromString("<p>hello from JavaScript</p>", "text/html"); return d.querySelector("p"); };</script>
</div>
</li>
</ul>
</div>
</body>
</html>
GET /js-method-element-demo.html HTTP/2
Host: 127.0.0.1
Range: selector=main
HTTP/2 206
content-range: selector main
hello from JavaScript
Passing arguments
Attributes on the method element become arguments to the method — named local variables in Sessel, positional arguments in JavaScript:
<t:greet name="Ada"></t:greet>
The matching schema:
<li itemprop="property" itemscope itemtype="https://pagelove.org/Method">
<meta itemprop="name" content="greet">
<meta itemprop="returns" content="https://pagelove.org/Element">
<li itemprop="parameter" itemscope itemtype="https://pagelove.org/Parameter">
<meta itemprop="name" content="name">
<meta itemprop="type" content="https://schema.host/Text">
</li>
<div itemprop="implementation" itemscope itemtype="https://pagelove.org/Sessel">
<script itemprop="source" type="text/sessel">
new p { "Hello, " + name + "!" }
</script>
</div>
</li>
The dispatcher matches attribute names against declared parameter names. Attributes whose names don't match a parameter (and aren't xmlns declarations) are ignored.
When two methods share a name but declare different parameters, the dispatcher picks the overload whose declared parameters all appear as attributes — the most specific match wins.
doesNotUnderstand fallback
If a schema declares a method literally named doesNotUnderstand, it catches every unmatched dispatch under bound prefixes for that schema — from either invocation form. It always receives messageName (the unrecognised local name), but the shape of parameters differs by the form that triggered the dispatch:
| Invocation form | parameters shape |
|---|---|
Element form (<t:foo attr="v">) |
A list of { name, value } maps, one per attribute on the element whose name doesn't begin with xmlns: — note this excludes a prefixed xmlns: declaration but not a bare xmlns="…" attribute, which is passed through like any other. |
Attribute form (<div t:foo="v">) |
A list containing a single bare string — the dispatching attribute's value. |
A Sessel doesNotUnderstand reads messageName and parameters as named locals; a JavaScript doesNotUnderstand declares them as parameters (messageName, then parameters) and receives them positionally. In both languages, parameters itself is shaped per the table above — code that reads parameters[0] gets a bare value under the attribute form but a { name, value } map under the element form.
This is the only escape hatch when you want a single method to handle many element or attribute names — for example, a <t:any-tag>-style passthrough, or (as the built-in Binding/CSS schema does) a css:* attribute whose value is evaluated as a CSS selector regardless of its local name. Without it, the two invocation forms fail differently for an undeclared name under a bound prefix: the element form fails composition with an HTTP 500 (see Error cases); the attribute form fails silently — the attribute is simply stripped from the output with no dispatch and no error.
Attribute-form example
The built-in Binding/CSS schema — the mechanism behind Resource Binding's r: attributes — is itself just a doesNotUnderstand method reached through this attribute-form dispatch:
<li itemprop="property" itemscope itemtype="https://pagelove.org/Method">
<meta itemprop="name" content="doesNotUnderstand">
<div itemprop="implementation" itemscope itemtype="https://pagelove.org/Sessel">
<script itemprop="source" type="text/sessel">
@schema Selector url("https://pagelove.org/Selector");
let result = new Selector { selector: parameters[0] }.execute();
Context[messageName] = result;
result
</script>
</div>
</li>
An r:items="li" attribute has no declared items method, so it dispatches to this doesNotUnderstand: messageName is "items" and parameters is ["li"] (the bare-string shape from the table above). The method evaluates parameters[0] as a CSS selector and binds the result into Context.items, which a sibling p:template — dispatched after it, per the ordering rule above — can then render. See Resource Binding for the full worked example.
End-to-end testable example. A minimal urn:AttrDemo schema declares only doesNotUnderstand, so any t:*="…" attribute other than the reserved t:transient marker reaches it (see the transient exception above); the schema is provided inline so a single PUT registers both it and the page.
Starting with /attr-form-demo.html as
PUT /attr-form-demo.html HTTP/2
Host: 127.0.0.1
Content-Type: text/html
<!DOCTYPE html>
<html xmlns:t="urn:AttrDemo">
<body>
<main>
<div t:greet="Ada"></div>
</main>
<div hidden itemscope itemtype="https://pagelove.org/AuthorizationRule">
<meta itemprop="actor" content="*">
<meta itemprop="resource" content="/*">
<meta itemprop="method" content="GET">
<meta itemprop="action" content="allow">
</div>
<div hidden itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="urn:AttrDemo">
<ul>
<li itemprop="property" itemscope itemtype="https://pagelove.org/Method">
<meta itemprop="name" content="doesNotUnderstand">
<meta itemprop="returns" content="https://pagelove.org/Element">
<div itemprop="implementation" itemscope itemtype="https://pagelove.org/Sessel">
<script itemprop="source" type="text/sessel">
new p { messageName + ": " + parameters[0] }
</script>
</div>
</li>
</ul>
</div>
</body>
</html>
GET /attr-form-demo.html HTTP/2
Host: 127.0.0.1
Range: selector=main
HTTP/2 206
content-range: selector main
greet: Ada
The <div t:greet="Ada"> has no declared greet method on urn:AttrDemo, so it dispatches to doesNotUnderstand: messageName is "greet" and parameters is ["Ada"] — the bare-string shape from the table above, not a { name, value } map. Because the method's returns type is https://pagelove.org/Element and the result isn't Null, the whole <div> — not just the t:greet attribute — is replaced by the returned <p>greet: Ada</p>, per the whole-host-element replacement rule described above.
Error cases
The two forms fail differently when a prefix is unbound or a name is undeclared — the element form always errors, the attribute form never does. Declaring doesNotUnderstand on the schema (see doesNotUnderstand fallback) catches an undeclared name under a bound prefix identically in both forms; it cannot help with an unbound prefix, since the prefix itself isn't recognised in either form.
| Condition | Form | Result |
|---|---|---|
The xmlns prefix is unbound (e.g. typo: xmlsns:t="urn:Test") |
Element | The element's prefix doesn't appear in the dispatch table. HTTP 500: no method found for <prefix>:<name> and no doesNotUnderstand defined. |
| The xmlns prefix is unbound | Attribute | No dispatch is attempted and no error is raised — the attribute is left in the output exactly as written. |
The schema isn't loaded into the host's cache, or declares no matching method and no doesNotUnderstand |
Element | Same HTTP 500 as an unbound prefix — the method exists in the page's inline schema but the cache hasn't been refreshed, or the name is genuinely undeclared. PUT the document containing the schema once to register it. |
The schema isn't loaded into the host's cache, or declares no matching method and no doesNotUnderstand |
Attribute | No error. The attribute is silently stripped from the output with no dispatch — see doesNotUnderstand fallback. |
The method has no implementation |
Both | The dispatcher invokes a method whose source is empty; the result is Null and the element (element form) or dispatching attribute (attribute form) is removed. |
The implementation raises an error (a Sessel error, or a thrown JavaScript error such as a NoModificationAllowedError from mutating the read-only document) |
Both | The composition fails with HTTP 500 and the error message. |
| Composition budget exhausted (500 dispatches per request) | Both | HTTP 503 composition budget exceeded. Each method element or dispatched attribute costs one budget unit; recursive results cost more. |
Example
End-to-end testable example. The schema is provided inline in the test page so a single PUT registers both the schema and the page.
With /method-element-demo.html set to
PUT /method-element-demo.html HTTP/2
Host: 127.0.0.1
Content-Type: text/html
<!DOCTYPE html>
<html xmlns:t="urn:Demo">
<body>
<main>
<t:hello></t:hello>
</main>
<div hidden itemscope itemtype="https://pagelove.org/AuthorizationRule">
<meta itemprop="actor" content="*">
<meta itemprop="resource" content="/*">
<meta itemprop="method" content="GET">
<meta itemprop="action" content="allow">
</div>
<div hidden itemscope itemtype="https://pagelove.org/Schema">
<meta itemprop="type" content="urn:Demo">
<ul>
<li itemprop="property" itemscope itemtype="https://pagelove.org/Method">
<meta itemprop="name" content="hello">
<meta itemprop="returns" content="https://pagelove.org/Element">
<div itemprop="implementation" itemscope itemtype="https://pagelove.org/Sessel">
<script itemprop="source" type="text/sessel">
new p { "hello from urn:Demo" }
</script>
</div>
</li>
</ul>
</div>
</body>
</html>
GET /method-element-demo.html HTTP/2
Host: 127.0.0.1
Range: selector=main
HTTP/2 206
content-range: selector main
hello from urn:Demo
The <t:hello> is replaced by the method's returned <p> fragment. The Range: selector=main reads only the <main> subtree to keep the assertion focused on the dispatched output.
See also
- Stamp —
<p:stamp>, the built-in method element on the Pagelove namespace that emits a boundContextvalue into the document. - Includes — pull a fragment of another document into the current page (declarative, no Sessel).
- Expression Binding — bind a Sessel value to an element via an
e:attribute, without a Schema-defined Method. - Resource Binding — bind the result of a CSS selector query to a named variable.
- JavaScript expression binding — bind a JavaScript expression's value to an element via a
j:attribute, without a Schema-defined Method. - Property — Schema property declarations (Method is a kind of Property item with a different
itemtype). - Transient Elements — the
prefix:transientmarker attribute, which the attribute-form dispatcher always skips. - Sessel reference — language details for a Sessel
implementationbody. - Schema definitions in HTML — declaring JavaScript bindings (defaults,
@read,@computed, methods) inside Schema HTML.