VIII. Directed Scoped Specifiers (DSS) - bahrus/trans-render GitHub Wiki

Directed Scoped Specifiers (DSS) and DSSArray's

"Directed Scoped Specifiers" (DSS) is a string pattern specification that is inspired by CSS selectors, but whose goal is far more targeted: It provides a syntax to:

  1. Make it easy to describe a relationship to another DOM element "in its vicinity", including the (custom element) host containing the element.
  2. Included in that information can be highly useful information including the name of the property to bind to, and the event name to listen for.
  3. It is compatible with HTML that could be emitted from template instantiation built into the browser, that adopts this proposal.
  4. It nudges the developer to name things in a way that will be semantically meaningful.

Special Symbols

At the core of DSS are some some special symbols used in order to keep the statements small.

Symbol Meaning Notes
/propName "Hostish" Attaches listeners to "propagator" EventTarget.
@propName Name attribute Listens for input events by default.
|propName Itemprop attribute If contenteditible, listens for input events by default. Otherwise, uses be-value-added.
#propName Id attribute Listens for input events by default.
%propName match based on part attribute Listens for input events by default.
-prop-name Marker indicates prop Attaches listeners to "propagator" EventTarget.
~elementName match based on element name Listens for input events by default.
$0 adorned element
::eventName name of event to listen for

|

What do we mean by "Hostish"?

"Host" is a generic term used to represent the custom element that "contains" the element in question. But that element in question may be inside a ShadowDOM element, or it might not be. We may even want to be able to easily switch back and forth between using ShadowDOM and not using ShadowDOM, without having to re-architect everything. But if there is no Shadow Root, how do we identify which element to look for? Here's the approach we take:

  1. First, do a "closest" for an element with attribute "itemscope".
    1. If such an element is found, and if the itemscope attribute specifies a value, the value is expected to point to the name of a custom element that can be found within the element somewhere, attainable via querySelector (ideally, the custom element should be placed at the first available location that doesn't break HTML decorum). That may serve as our candidate for host. Search for the element, wait for the element to upgrade if applicable. Create a weak reference to it for faster future searches. That may be our host [Untested]
    2. If the tag name has a dash in it, that may be the host. Wait for the element to upgrade.
    3. In either case, check if the specified property to bind to (if applicable) belongs to the host candidate. If so, that's the host. If not, keep traversing upwards.
  2. If no match found, use getRootNode().host.

We are often (but not always in the case of 2. below) making some assumptions about the elements we are comparing --

  1. The value of the elements we are comparing are primitive JS types that are either inferrable, or specified by a property path.
  2. The values of the elements we are comparing change in conjunction with a (user-initiated) event.

Directional Symbols

Symbol Meaning Notes
^{...} Single closest match
^^{...} Recursive closest match Keeps going up a level until it finds a matching DSS fulfilling element inside [TODO]
^{(...)} UpSearch Checks previous siblings as well as parent, previous elements of parent, etc.
Y{...} Single file downward match. Doesn't check inside each downward element [TODO]
Y{(...)} Thorough downward match. Checks stuff inside each downward element [TODO]

DSSArray's

We can combine multiple "atomic" DSS's, as described above, which generally don't contain any spaces, into a "molecule" of DSS expressions via some key words that do have spaces around them. We call such expressions "DSSArray's".

The key words are:

Key word Meaning Notes
and Allows for an array of specifiers The word is actually optional
as Specify type conversion number | boolean | \string | \regex | \url | object

By Example

DSS is used throughout many of the components / enhancements built upon this package. The best way to explain this lingua franca is by example

fetch-for

The fetch-for web-component uses DSS extensively:

<input name=op value=integrate>
<input name=expr value=x^2>
<fetch-for
    for="@op and @expr"
    onInput="
        event.href=`https://newton.now.sh/api/v2/${event.forData.op.value}/${event.forData.expr.value}`
    "
    target=-object
    onerror=console.error(href)
>
</fetch-for>
...
<json-viewer -object></json-viewer>

@op and @expr is saying "find elements within the nearest "form" element, or rootNode with name attributes "op" and "expr". Leave whatever default events and approaches of extracting the value from these elements up to the individual library to determine, that is outside the scope of DSS".

Likewise, the marker "-object" is saying "find element with attribute -object" and pass whatever this library wants to pass to it (say myStuff), via the local property oJsonViewer.object = myStuff".

Conditional display with be-switched

be-switched is a custom enhancement that can lazy load HTML content when conditions are met. It uses DSS syntax to specify dependencies on nearby elements (or the host). For example:

<label for=lhs>LHS:</label>
<input id=lhs>
<label for=rhs>RHS:</label>
<input id=rhs>
<template be-switched='on when #lhs equals #rhs.'>
    <div>LHS === RHS</div>
</template>

To specify more nuanced locations, use the "upstream" ^ operator:

These should be ignored:
<div>
    <label for=lhs>LHS:</label>
    <input id=lhs name=lhs>
    <label for=rhs>RHS:</label>
    <input id=rhs name=rhs>
</div>
These should be active:
<section>
    <label>
        LHS:
        <input name=lhs>
    </label>
    
    <label>RHS:
        <input name=rhs>
    </label>
    
    <template be-switched="on when ^{section}@lhs eq ^{section}@rhs.">
        <div>LHS === RHS</div>
    </template>
</section>

Specifying events

be-bound has an example where we specify the property name, and the event name to listen for:

<input id=alternativeRating type=number>
<form be-bound='between rating:value::change and #alternativeRating.'>
    <div part=rating-stars class="rating__stars">
        <input id="rating-1" class="rating__input rating__input-1" type="radio" name="rating" value="1">
        <input id="rating-2" class="rating__input rating__input-2" type="radio" name="rating" value="2">
        <input id="rating-3" class="rating__input rating__input-3" type="radio" name="rating" value="3">
        <input id="rating-4" class="rating__input rating__input-4" type="radio" name="rating" value="4">
        <input id="rating-5" class="rating__input rating__input-5" type="radio" name="rating" value="5">
    </div>  
</form>

You are now fully certified to use this library.

⚠️ **GitHub.com Fallback** ⚠️