npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

be-observant

v0.0.178

Published

Glue DOM and custom elements together via JSON declarations.

Downloads

442

Readme

be-observant 🔭 [WIP]

Observe properties of peer elements or the host.

be-observant takes less of a "top-down" approach to binding than traditional frameworks. It places less emphasis (but certainly not none) on binding exclusively from the (custom element) component container. Yes, it can do that, but it can also provide for "Democratic Web Component Organisms" where the host container acts as a very thin "Skin Layer" which can be passed a small number of "stimuli" values into. Inside the body of the web component, we might have a non visible "brain" component that dispatches events. be-observant allows other peer elements within the "body" to receive messages that the brain component emits, without forcing the outer "skin" layer to have to micromanage this all.

NPM version How big is this package in your project? Playwright Tests

[!Note] An extra thin layer can be applied on top of be-observant, so that the original HTML that is streamed from the server can provide the initial values of the property that be-observant observes, and then once that initial handshake is established, lean exclusively on be-observant for all subsequent updates. This is handled by be-entrusting.

The most quintessential example

The example below follows the traditional "pass props down" from the host approach, only really it is "pulling props in". You say tomāto, I say tomäto kind of thing.

<mood-stone>
    #shadow
    <input 
        name=isHappy 
        disabled 
        type=checkbox  
        be-observant>
</mood-stone>

What this does: Observes and one-way passes mood-stone's isHappy property value to the input element's checked property.

be-observant is making a few inferences:

  1. The name of the input element ("isHappy") will match with the name of the host property from which we would want to bind it. Why adopt confusing mappings if we can possibly avoid it?
  2. Since the type of input element is of type checkbox, it sets the local "checked" property to match the "isHappy" property from the host.

[!Note] be-observant is a rather lengthy word to have to type over and over again, and this element enhancement would likely be sprinkled around quite a bit in a web application. The name is registered in the optional file behivior.js so to use whatever name makes sense to you (🔭, be-obs?) within your application, just don't reference that file, and instead create and reference your own registration file. Names can also be overridden within a Shadow scope as well. Throughout much of the rest of this document, we will use 🔭 instead of be-observant, and ask that you make a "mental map" of 🔭 to "be-observant". In fact, this package does provide an alternative registration file, 🔭.js, that registers the enhancement via attribute "🔭". The developer could easily copy/modify an additional registration file, to adopt their own preferred name.

If you only use this enhancement once in a large application, spelling out the full name (and referencing the canonical behivior.js file) would probably make the most sense, for "locality of behavior" reasons, and also tapping into google searches. But I would strongly consider using a (custom) shortcut in any application that intends to rely on this enhancement in a heavy way.

Back to our quintessential example

As we already discussed, in the example above, we made the assumption that if the user gives the input element name "isHappy", that the choice of name will most likely match the identical property name coming from the host web component container.

If this assumption doesn't hold in some cases, then we can specify the name of the property we want to observe from the host:

Specifying the host property to observe

<mood-stone>
    #shadow
    <input 
        type=checkbox 
        disabled 
        be-observant='of /isHappy.'
    >
</mood-stone>

Now that we've spelled out the full word twice (be-observant), from now on, we will use "🔭" as our shortcut for be-observant, but please apply the mental mapping from 🔭 to the full name, for the statements to make the most sense.

The slash ("/") symbol indicates to get the value from the host. If omitted, it is assumed:

Reducing cryptic syntax

<mood-stone>
    #shadow
    <input 
        type=checkbox 
        disabled 
        🔭='of isHappy.'
    >
</mood-stone>

Hosts that do not use shadow DOM.

If Shadow DOM is not used, add the "itemscope" attribute so that be-observant knows what to look for:

<mood-stone itemscope>
    <input 
        type=checkbox 
        disabled 
        🔭='of isHappy.'
    >
</mood-stone>

Binding based on microdata attribute.

<mood-stone>
    <template shadowrootmode=open>
        <div itemscope>
            <span itemprop=isHappy 🔭></span>
        </div>

        <xtal-element
            prop-defaults='{
                "isHappy": true
            }'

        ></xtal-element>
        <be-hive></be-hive>
    </template>
</mood-stone>

This sets the span's textContent to the .toString value of moon-stone's isHappy property, and monitors for changes, i.e. one-way binds.

By Id also works:

<mood-stone>
    <template shadowrootmode=open>
        <div itemscope>
            <span itemprop=isHappy></span>
        </div>
        <input 
            id=isHappy 
            disabled 
            type=checkbox  
            🔭
        >
        <xtal-element
            prop-defaults='{
                "isHappy": true
            }'
            xform='{
                "| isHappy": 0
            }'
        ></xtal-element>
        <be-hive></be-hive>
    </template>
</mood-stone>

Note that the itemprop attribute takes precedence over the name attribute, which takes precedence over the id attribute.

DSS Specifier Syntax

In the example above, we mentioned using the / symbol to indicate to observe a property from the host. But be-observant can also observe peer elements within the ShadowRoot (or outside any shadow root be-observant adorns an element sitting outside any ShadowRoot).

The syntax adopts what we refer as the DSS specification, where DSS stands for "directed scoped specifier". It is inspired by CSS selectors, but it is optimized for binding scenarios.

This is documented in (increasingly) painstaking detail where the library is maintained.

Binding to peer elements

Now we will start to see how be-observant provides for more "grass-roots" democratic organism (web component) support.

By name attribute

<input name=search type=search>

<div 🔭='of @search.'></div>

As the user types in the input field, the div's text content reflects the value that was typed.

The search for the element with name=search is done within the closest form element, and if not found, within the (shadow)root node.

By id

This also works:

<input id=searchString type=search>

<div 🔭='of #searchString.'></div>

The search for element with id=searchString is done within the (shadow)root node, since id's are supposed to be unique with a (shadow)root node.

By markers

<mood-stone>
    #shadow
    <my-peer-element -some-bool-prop></my-peer-element>
    <input 
        type=checkbox 
        disabled 
        🔭='of -some-bool-prop'
    >
</mood-stone>

This observes the my-peer-element's someBoolProp property for changes and sets the adorned element's checked property based on the current value.

By itemprop

<link itemprop=isHappy href=https://schema.org/True>

...

<input 
    type=checkbox 
    🔭='of |isHappy.'
>

What this does: If necessary, auto attaches the be-value-added enhancement to the link element, which recognizes the True/False values of schema.org as far as the href attribute, and provides a property oHTMLLinkElement.beValueAdded.value through which updated values can be passed / listened to. Essentially it provides a hidden boolean "signal" we can bind to and also use for styling purposes.

The editable checkbox element can observe changes to this "signal".

We saw earlier that we can adorn elements with the itemprop attribute with 🔭 attribute, and it will automatically pull in values from the host. This allows us to create a code-free "chain" of bindings from the host to Shadow children, and from the Shadow children to peer elements.

Specifying the property to assign the observed value(s) to.

What we've seen above is a lot of mind reading about what our intentions are, as far as how we want to apply what we are observing to the element adorned by be-observant. Sometimes we are setting the "checked" property. Sometimes we are setting the textContent.

But sometimes we need to be more explicit because it isn't always transparent what we intend.

Single mapping from what to observe, specifying the property to target.

<input name=someCheckbox type=checkbox>

<my-peer-element enh-🔭='
    and set someBoolProp from @someCheckbox.
    '></my-peer-element>

This watches the input element for input events and passes the checked property to someBoolProp of oMyPeerElement. The word "and" is optional, there to allow for people who like to read complete sentences (including the (mentally mapped) attribute name)

The enh- prefix is there to avoid possible conflicts with attributes recognized by my-peer-element, in the absence of any tending loving care from the platform.

Multiple parallel observers

This example works, where each observing statement is treated independently:

<input name=someCheckbox type=checkbox>
<input name=someOtherCheckbox type=checkbox>

<mood-stone enh-🔭='and set isHappy from @someCheckbox.
    Set isWealthy from @someOtherCheckbox.
'>
    <template shadowrootmode=open>
        <div itemscope>
            is happy
            <div itemprop=isHappy></div>
            is wealthy
            <div itemprop=isWealthy></div>
        </div>
        <xtal-element infer-props
            prop-defaults='{
                "isHappy": true,
                "isWealthy": false
            }'
            xform='{
                "| isHappy": 0,
                "| isWealthy": 0
            }'
        ></xtal-element>
        <be-hive></be-hive>
    </template>
</mood-stone>

Unionizing

If multiple remote endpoints are observed that map to a single local prop, by default, the "truthy" conjunction (&&) is applied to them all. This will often result in passing in the value of the last property, unless the properties are actual booleans as they are below:

<input name=someCheckbox type=checkbox>
<input name=someOtherCheckbox type=checkbox>

<mood-stone enh-🔭='and set isHappy from @someCheckbox and @someOtherCheckbox.'>
    <template shadowrootmode=open>
        <div itemscope>
            is happy
            <div itemprop=isHappy></div>
        </div>
        <xtal-element 
            prop-defaults='{
                "isHappy": true,
            }'
            xform='{
                "| isHappy": 0,
            }'
        ></xtal-element>
        <be-hive></be-hive>
    </template>
</mood-stone>

In other words, in this example, the mood-stone's "isHappy" property will be set if either checkbox is checked.

The number of things we can observe is limited only by when the developer tires of typing the word "and".

be-observant also support additional ways of combining multiple remote endpoints into one local prop.

They are:

  1. Union

<mood-stone enh-🔭='(and) set isHappy to the union of @someCheckbox and @someOtherCheckbox.'>

  1. Sum [Untested]

<mood-stone enh-🔭='and set mySum to the sum of @someNumericInput and @someOtherNumericInput.'>

  1. Product [Untested]

<mood-stone enh-🔭='and set myProduct to the product of @someNumericInput and @someOtherNumericInput.'>

  1. Interpolation [TODO]

<mood-stone enh-🔭='and set sentenceProp to ${0} eats $1} weaving in @name and @food.'>

  1. Object Assignment [Untested]

<mood-stone enh-🔭='and set myObjectProp to an object structure by assigning @name and @food.'>

For the power hungry JS-firsters

As our business logic becomes more complex, I suspect many readers will begin asking themselves:

This is all great, but what if I just want to do some coding? Why learn all this contrived syntax?

Fair enough.

We now provide an interlude where we indicate how to inject JavaScript into the picture, and set properties, and derive properties as we need, with full, unfettered access to the JavaScript run time.

Scripting bravely

be-observant empowers the developer to tap into the full power of the JavaScript run time engine by adding script to the onload event of the adorned element.

If we know that this enhancement is the only enhancement affecting the adorned element that leverages the onload event, we can skip some defensive maneuvers that avoid collisions with other enhancements (discussed in the next example), resulting in a fairly compact script:

<label>
    Name:
    <input name=name>
</label>
<label>
    Food:
    <input name=food>
</label>

<mood-stone enh-🔭='of @name and @food.'
    onload="
        const {factors, setProps} = event;
        Object.assign(setProps, {
            myFirstProp: `${factors.name} eats ${factors.food}`,
        });
    "
>
    <template shadowrootmode=open>
        <div itemscope>
            <div itemprop=myFirstProp></div>
        </div>
        <xtal-element infer-props></xtal-element>
        <be-hive></be-hive>
    </template>
</mood-stone>

Note that it is also to add event listener to the adorned element (mood-stone in this case) with event 'load' and add additional properties to set.

Scripting defensively

If using an enhancement from the be-enhanced family of behaviors/enhancements, a key identifier that distinguishes enhancements from one another is the "enh" key. So this is how to code the onload event in such a way that it doesn't interfere with any other enhancements that make use of the onload event. Basically, just an added if condition:

<label>
    Name:
    <input name=name>
</label>
<label>
    Food:
    <input name=food>
</label>

<mood-stone enh-🔭='of @name and @food.'
    onload="
        const {factors, setProps, enh} = event;
        switch(enh){
            case '🔭':{
                Object.assign(setProps, {
                    myFirstProp: `${factors.name} eats ${factors.food}`,
                });
            }
        }

    "
>
    <template shadowrootmode=open>
        <div itemscope>
            <div itemprop=myFirstProp></div>
        </div>
        <xtal-element infer-props></xtal-element>
        <be-hive></be-hive>
    </template>
</mood-stone>

Attaching and setting other enhancement values [TODO]

<input name=search type=search>

<div 🔭='
    and set +beSearching:forText from @search.
'>
    supercalifragilisticexpialidocious
</div>

The plus symbol: + is indicating to tap into a custom enhancement.

The example above happens to refer to this enhancement.

Observing a specified property of a peer custom element

<tr itemscope>
    <td>
        <my-item-view-model></my-item-view-model>
        <div 🔭="of ~myItemViewModel:myProp1.">My First column information</div>
    </td>
    <td>
        <div 🔭="of ~myItemViewModel:myProp2."></div>
    </td>
</tr>

The search for the my-item-view-model custom element is done within the closest "itemscope" attribute.

This can be useful for scenarios where we want to display repeated data, and can't use a custom element to host each repeated element (for example, rows of an HTML table), but we want to provide a custom element as the "view model" for each row.

This will one-way synchronize my-item-view-model's myProp 1/2 values to the adorned element's textContent property.

Inferring the property to observe from a peer custom element

This also works:

<tr itemscope>
    <td>
        <my-item-view-model></my-item-view-model>
        <div itemprop=myProp1 🔭="of ~myItemViewModel.">My First column information</div>
    </td>
    <td>
        <div itemprop=myProp2 🔭="of ~myItemViewModel."></div>
    </td>
</tr>

We can specify what property of the peer custom element to bind to as follows:

Negation [TODO]

<mood-stone>
    #shadow
    
    <input name=someCheckbox type=checkbox>

    <my-peer-element enh-🔭='
        and set someBoolProp from the negation of @someCheckbox.
        '></my-peer-element>
</mood-stone>

Toggle [TODO]

To simply toggle a property anytime the observed element changes:

<mood-stone>
    #shadow
    
    <input name=someCheckbox type=checkbox>

    <my-peer-element enh-🔭='
        and toggle someBoolProp on @someCheckbox::input.
        '></my-peer-element>
</mood-stone>

PlusEq, MinusEq, TimeEq, DivEq [TODO]

Increment, Decrement [TODO]

Interpolating [TODO]

<mood-stone>
    #shadow
    
    <input name=name>
    <input name=food>

    <my-peer-element enh-🔭='
        and set mySecondProp to `$1 eats $2` from @name and @food.
        '></my-peer-element>
</mood-stone>

Adding / removing css classes / styles / parts declaratively [TODO]

<mood-stone>
    #shadow
    
    <input name=yourCheckbox type=checkbox>
    <input name=myCheckbox type=checkbox>

    <my-peer-element enh-🔭='
        Of @yourCheckbox and @myCheckbox.
        Set my-class to $1.
        SetClass my-second-class to $2.
        '></my-peer-element>
</mood-stone>

Since we are binding to booleans, adds class if true, otherwise removes.

If we bind to a string, simply sets the class to the value of the string.

Same with SetPart, SetStyle

Observing a specified property [TODO]

<my-peer-element></my-peer-element>

<your-peer-element enh-🔭="
    of ~myPeerElement:myProp.
    Set yourProp.
">

This will one-way synchronize my-peer-element's myProp value to the adorned element's yourProp property.

Example 5 Mapping [TODO]

Example 6

<-->

[!Note] be-observant provides similar functionality to be-bound. The difference is be-bound provides two-way binding between the adorned element and an upstream element, whereas be-observant is strictly one-way. Because it is one way, be-observant can apply some declarative adjustments to the value it is observing before applying to the adorned element.

Viewing Demos Locally

Any web server that can serve static files will do, but...

  1. Install git.
  2. Fork/clone this repo.
  3. Install node.js.
  4. Open command window to folder where you cloned this repo.
  5. npm install

  6. npm run serve

  7. Open http://localhost:3030/demo/ in a modern browser.

Running Tests

> npm run test

Using from ESM Module:

import 'be-observant/behivior.js';

or

import 'be-observant/🔭.js';

Using from CDN:

<script type=module crossorigin=anonymous>
    import 'https://esm.run/be-observant';
</script>