Package Exports
- be-switched
- be-switched/be-switched.js
Readme
be-switched [WIP]
be-switched is a template element enhancement that lazy loads content when conditions are met.
It is a member of the be-enhanced family of enhancements, that can "cast spells" on server rendered content, but also apply the same logic quietly during template instantiation.
The basic functionality
be-switched can be used in two modes:
- It can switch the template "on and off" based on comparing two values (lhs and rhs).
- Or it can switch the template "on and off" based on the values of peer microdata or form elements, or "boolish" properties coming from the host or upstream peer elements.
We will look at both options closely, starting with...
Comparing two values - JavaScriptObjectNotation
Example 1
<!-- Example 1 -->
<template id=template be-switched='{
"lhs": 37,
"rhs": "hello"
}'>
<div>lhs === rhs</div>
</template>
<button onclick=setLHS()>Set lhs = "hello"</button>
<script>
function setLHS(){
template.beEnhanced.beSwitched.lhs = 'hello';
}
</script>"lhs" stands for left-hand-side. "rhs" stands for "right-hand-side".
The default values for these two properties is lhs=false/rhs=true. So this allows for a simple, single "if" statement, as well as an "if not" statement.
[!NOTE] By default, property "beBoolish" is set to true, which means that if either the lhs or rhs value is a boolean, the equality check is made using truthy/falsy criteria, rather than an exact match of boolean values.
Since the lhs (37) doesn't equal the rhs ("hello"), the content inside the template remains inside the template. The moment the lhs equals the rhs, the content inside the template is appended adjacent to the template element. If the lhs later becomes unequal to the rhs again, the live DOM content that came from the template is hidden via css.
Now how can we change the values of the lhs and rhs? Normally, a framework can pass values to the top level of a web component / built-in element. Some frameworks may be able to pass values to sub properties. With such frameworks, they could, theoretically, pass updated values like so (under the hood):
await customElements.whenDefined('be-enhanced');
oTemplate.beEnhanced.by.beSwitched.rhs = 37;The extra ".by" is necessary just in case beSwitched hasn't been attached to the element yet.
The first line can be avoided if we already know be-enhanced has been registered.
Another way to pass in the value reliably is thusly:
if(oTemplate.beEnhanced === undefined) oTemplate.beEnhanced = {};
if(oTemplate.beEnhanced.beSwitched === undefined) oTemplate.beEnhanced.beSwitched = {};
oTemplate.beEnhanced.beSwitched.rhs = 37;All of this is to say, most frameworks probably don't and won't be able to make it trivially easy to pass values to the enhancement, especially for unbundled applications that make use of the dynamic import(), so that the timing of when dependencies load is unpredictable.
Frameworks fail us, yet again!
For that reason, among others, an alternative way of "pulling in" values to compare is provided via:
Hemingway Notation
Special Symbols
In the examples below, we will encounter special symbols used in order to keep the statements small:
| Symbol | Meaning | Notes |
|---|---|---|
| /propName | "Hostish" | Attaches listeners to getters/setters. |
| @propName | Name attribute | Listens for input events. |
| propName | Itemprop attribute | |
| #propName | Id attribute | Listens for input events. |
| .propName | css class match | Listens for input events. [TODO] |
| %propName | match based on part attribute | Listens for input events. |
| -prop-name | Marker indicates prop | Attaches listeners to getters/setters. |
"Hostish" means:
- First, do a "closest" for an element with attribute itemscope, where the tag name has a dash in it. Do that search recursively.
- If no match found, use getRootNode().host.
Example 2a
<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>Example 2b
<form>
<label>
LHS:
<input name=lhs>
</label>
<label>RHS:
<input name=rhs>
</label>
<template be-switched='on when @lhs equals @rhs.'>
<div>LHS === RHS</div>
</template>
</form>Example 2c
<div itemscope>
<span itemprop=lhs contenteditable></span>
<span itemprop=rhs contenteditable></span>
<template be-switched='on when |lhs equals |rhs.'>
<div>LHS === RHS</div>
</template>
</div>Examples 2* all focused on comparing two values. The reason for focusing first on what is the "harder" case, is simply to suggest why this enhancement was so named.
But what if we just want to lazy load content when a single value goes from "false" to "true"? This package supports that as well.
Boolean conditions based on peer elements or host
If all you are trying to do is to instantiate (and then hide, as conditions change) a template depending on a single truthy value of a peer element, use the following syntax:
Example 3a
<div itemscope>
...
<link itemprop=isHappy href=https://schema.org/True>
...
<template be-switched='on when | is happy. //or |isHappy.' >
<my-content></my-content>
</template>
</div>Can have multiple such statements -- or condition. Each sentence can begin with "on" or "On", whichever seems more readable.
Example 3b
Can also reference form element, or form associated custom elements
<form>
...
<input name=isHappy type=checkbox>
...
<template be-switched='on when @ is happy.'>
<my-content></my-content>
</template>
</form>Checks for $0.checked, if undefined, checks for $0.ariaChecked. Listens for input events.
Example 3c
<form>
...
<input id=isHappy type=checkbox>
...
<template be-switched='on when # is happy.'>
<my-content></my-content>
</template>
</form>Example 3d
<form>
...
<input id=isHappy>
...
<template be-switched='on only when # is happy.'>
<my-content></my-content>
</template>
</form>This is an "and" condition due to the presence of "only"
Example 3e binding based on part attribute
<form>
<input part=isHappy type="checkbox">
<template be-switched='on when % is happy.'>
<my-content id=myContent></my-content>
</template>
</form>Example 3f binding based on class attribute. [NOTTODO?]
So this is where we have a clash between Hemingway and CSS. The most natural symbol to use for a class selector would be the period ("."). However, because the period is used to break up statements, that would require an escape character of some sort, or using some other symbol to represent the class query.
After staring at my keyboard for several hours, I have decided that maybe this is for the best. Using css classes for purposes of binding may cross a barrier into "hackish" territory, especially when there are so many attractive alternatives that we've discussed above. The part attribute is already skating on thin ice, but I think, in the context of a web component, may make sense to use sometimes, as the purpose of the part is more "public" and I think will tend to be more semantic as far as the nature of the element it adorns.
Example 4a
"/" refers to the host.
<mood-stone>
#shadow
<template be-switched='on when / is happy.'>
<my-content></my-content>
</template>
<be-hive></be-hive>
</mood-stone>This also works:
Example 4b
<mood-stone>
#shadow
<template be-switched='on when /isHappy.'>
<my-content></my-content>
</template>
<be-hive></be-hive>
</mood-stone>/ is considered the "default" symbol, so it actually doesn't need to be specified:
Example 4c
<mood-stone>
#shadow
<template be-switched='on when is happy.'>
<my-content></my-content>
</template>
<be-hive></be-hive>
</mood-stone>Example !2a - !4a [Not Fully Tested]
All the examples above also work, but instead of "on", use "off", which of course means the negation is performed.
Viewing Your Element Locally
Any web server that can serve static files will do, but...
- Install git.
- Fork/clone this repo.
- Install node.js
- Open command window to folder where you cloned this repo.
npm install
npm run serve
- Open http://localhost:3030/demo/ in a modern browser.
Running Tests
> npm run testUsing from ESM Module:
import 'be-switched/be-switched.js';Using from CDN:
<script type=module crossorigin=anonymous>
import 'https://esm.run/be-switched';
</script>P.S.
These are scenarios that may be supported in the future. For now, these can be achieved with the help of be-linked
Example 5 [TODO]
<ways-of-science>
<largest-scale>
<woman-with-carrot-attached-to-nose></woman-with-carrot-attached-to-nose>
</largest-scale>
<largest-scale>
<a-duck></a-duck>
</largest-scale>
<template be-switched='
On when value property of previous largest-scale element
having inner woman-with-carrot-attached-to-nose element
equals value property of previous largest-scale element
having inner a-duck element.
'
>
<div>A witch!</div>
<div>Burn her!</div>
</template>
</ways-of-science>[!NOTE] The dashes are optional, I think it makes the meaning more clear, depending.
Compatibility with server-side-rendering [TODO]
be-switched is compatible with server-side-rendering if the following approach is used:
If during the time the SSR is taking place, the desire is not to display the content, but rely on the client to lazy load when conditions warrant, then the syntax above is exactly what the SSR should generate.
If, however, the content should display initially, but we want the client-side JavaScript to be able to hide / disable the content when conditions in the browser change, the server should render the contents adjacent to the template, and leverage standard microdata attributes, to establish artificial hierarchies.
<ways-of-science>
<largest-scale>
<woman-with-carrot-attached-to-nose></woman-with-carrot-attached-to-nose>
</largest-scale>
<largest-scale>
<a-duck></a-duck>
</largest-scale>
<template itemscope itemref="witch burn-her"
be-switched='
Off when value property of previous largest-scale element
having inner woman-with-carrot-attached-to-nose element
is not equal to the value property of previous largest-scale element
having inner a-duck element
.
'
>
<div>A witch!</div>
<div>Burn her!</div>
</template>
<div id=witch>A witch!</div>
<div id=burn-her>Burn her!</div>
</ways-of-science>We are using built-in support for microdata to signify a hierarchical relationship with a flat list of DOM elements.
In this scenario, repeating the content inside the template is unnecessary, unless the optional setting: deleteWhenInvalid is set to true.
Throwing elements out of scope away [Untested]
An option, minMem, allows for completely wiping away content derived from the template when conditions are no longer met. This might be better on a low memory device, especially if the content has no support for be-oosoom (see below).
Additional conditions be-switched supports
In addition to "if" boolean checks, and equality checks using lhs and rhs keys, some additional "if" checks can be made:
| Key | Meaning |
|---|---|
| ifMediaequals | Expands the template / makes visible only when the specified media query expression is satisfied. |
| checkIfNonEmptyArray | Tests if (bound) expression evaluates to a non empty array |
Lazy Loading / Hibernating [Untested]
be-switched can "go to sleep" when the template it adorns goes out of view, if the template is also decorated by be-oosoom. be-switched provides an option to toggle the inert property when the conditions become false, in lieu of deleting the content.
