Dropping into the primitives layer

This recipe shows how to use the low-level PLDocument and PLElement classes from pagelove/primitives.mjs to read and write individual elements programmatically, outside the declarative binding flow.

When to reach for primitives

Most applications use the Pagelove class and declarative bindings. The primitives layer is useful in a few specific situations:

If your use case involves a rendered page with live data, start with the Pagelove class instead. The primitives layer is what the high-level API uses internally.

Import the module

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

The module exports two classes: PLDocument (a document-level wrapper) and PLElement (an element-level wrapper that knows how to issue scoped HTTP requests).

Discover capabilities with OPTIONS

Before issuing requests, call OPTIONS() on a PLDocument to discover which elements the server allows you to interact with and which methods are permitted:

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

  const doc = new PLDocument('/notes.html');
  await doc.OPTIONS();
</script>

OPTIONS() issues an OPTIONS request with Accept: multipart/mixed. The server responds with a multipart body describing the selectors and allowed methods for each element. After OPTIONS() resolves, matching DOM elements have their allowed methods attached directly — element.GET(), element.PUT(), element.DELETE() — ready to call.

Read a specific element

To read a single element without capability discovery, construct a PLElement directly and call GET():

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

  const doc = new PLDocument('/notes.html');
  const el = new PLElement(doc.url, document.getElementById('note-42'));
  const node = await el.GET();

  console.log(node.textContent);
</script>

GET() issues a fetch with Range: selector=#note-42 (derived from the element's id), parses the response body as an HTML fragment, and returns the resulting DOM node. The selector is generated automatically from the element — preferring id, falling back through itemprop, class names, and positional selectors.

Write an element back

After modifying a node, call PUT() to send it back to the server:

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

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

  target.querySelector('[itemprop="title"]').textContent = 'Updated title';
  await el.PUT();
</script>

PUT() serializes the wrapped element's outerHTML and sends it as the request body. The Range header scopes the write to the element's selector. If the element has an ETag (populated by a previous GET or OPTIONS), If-Match is included for conditional update.

A complete example: logging an element's text

This script reads a specific heading from a document and logs its content:

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

  const doc = new PLDocument('/about.html');
  await doc.OPTIONS();

  const heading = document.querySelector('h1');
  if (heading && typeof heading.GET === 'function') {
    const fresh = await heading.GET();
    console.log('Heading text:', fresh.textContent);
  }
</script>

After OPTIONS() runs, the h1 element has GET attached if the server allows it. The script calls GET() to fetch the latest version and logs the text content.

See also