Primitives

pagelove/primitives.mjs is the low-level HTTP client that pagelove.mjs uses internally. Most apps do not touch it directly.

When to reach for it

For everything else, use the Pagelove class and the declarative flow on top.

Loading the library

<script type="module">
  import { PLDocument, PLElement } from 'https://pagelove.github.io/beta-js/pagelove/primitives.mjs';
</script>

The module exports two classes: PLDocument and PLElement.

The PLDocument class

A PLDocument represents a Pagelove resource URL and the live DOM associated with it.

Constructor

Form Bound document
new PLDocument() window.location.href — bound to window.document
new PLDocument(url) The given URL. If it matches window.location.href, the live document is bound; otherwise the document is fetched and parsed lazily.

Methods

Method Description
OPTIONS() Issues an OPTIONS request with Accept: multipart/mixed, parses the multipart response, and dispatches a PLCapability event on every node matching a declared selector.
createElement(element) Returns a new PLElement wrapping the given DOM node.
req(method, ...opts) Returns a bare Request targeting the document URL.

Properties

Property Type Description
url string The URL this document is bound to.
document Promise<Document> Resolves to the bound DOM document. Fetches and parses the HTML on first access if no live document is bound.

A PLDocument bound to a live Document stores a WeakRef to itself on document.pagelove and installs listeners for PLCapability and PLMethodCompleted.

The PLElement class

A PLElement wraps a single DOM node and knows how to issue HTTP requests scoped to that node.

Constructor

Form Effect
new PLElement(url) Creates an element bound to the URL with no DOM node attached yet.
new PLElement(url, element) Binds to the given DOM node. The element's ownerDocument becomes the associated document.

Methods

Method Description
GET() Fetches the element fragment, parses the response body as a single HTML node, and returns the new node.
PUT(body?) Replaces the element on the server. Body defaults to element.outerHTML; a Node body is serialized via outerHTML. Returns the raw Response.
POST(body) Posts a child fragment. The response body is parsed as a single HTML node and appended as a child of the wrapped element (unless the supplied body was already a live attached node). Throws if no body is supplied.
DELETE() Deletes the element on the server and removes it from the DOM on success.
req(method, opts) Builds the underlying Request object — sets the Range header, conditionally sets If-Match, and merges the caller's opts.

Properties

Property Type Description
url string The document URL the element is scoped against.
element Element The wrapped DOM node. Setting it also sets document from the node's ownerDocument.
document Document The DOM document the wrapped element belongs to.
selector string A stable CSS selector generated from the element. Prefers id, anchors to the nearest ancestor with an id, and falls back through itemprop, class names, role, and :nth-child(...).

OPTIONS lives on PLDocument, not PLElement — discovery is document-level.

Attached methods on DOM elements

PLDocument listens for PLCapability events on its bound document. The event payload looks like:

{
  selector: '<css selector>',
  allow: ['GET', 'PUT', 'POST', 'DELETE']
}

For each method named in allow, the document constructs a PLElement for the event target and attaches the matching method directly to the DOM node as a non-writable, configurable property:

Object.defineProperty(target, 'PUT', { value: plElement.PUT.bind(plElement), ... });

After the flow has run, element.GET(), element.PUT(body), element.POST(body), and element.DELETE() are callable on the DOM node itself with no further imports. Only methods the server has authorized appear.

ETag handling

ETags are loaded lazily, only for elements that need them.

Request format

Every request a PLElement issues is built by req(method, opts):

Header Value When
Range selector=<generated-selector> Always
If-Match <element.etag> When element.etag is a string and the method is not POST

Everything else is a standard fetch() Request. Each request also dispatches PLMethodStarted before sending and PLMethodCompleted after the response arrives. Both events bubble and carry { method, selector }; PLMethodCompleted additionally carries response.

Examples

Calling an attached method

<script type="module">
  import { PLDocument } from 'https://pagelove.github.io/beta-js/pagelove/primitives.mjs';

  const doc = new PLDocument();
  await doc.OPTIONS();

  document.addEventListener('click', async (event) => {
    const card = event.target.closest('article.note');
    if (card && typeof card.DELETE === 'function') {
      await card.DELETE();
    }
  });
</script>

Once OPTIONS() has run, any matching <article class="note"> exposes the methods the server allows.

Manual PLDocument and PLElement

<script type="module">
  import { PLDocument } from 'https://pagelove.github.io/beta-js/pagelove/primitives.mjs';

  const doc = new PLDocument('/notes.html');
  const target = document.getElementById('note-42');

  const note = await doc.createElement(target);
  await note.PUT('<article id="note-42">Edited</article>');
</script>

The wrapper is constructed by hand and no capability discovery is required. Range: selector=#note-42 is generated from the element's id; If-Match is added automatically once note.element.etag is populated.

See also