Giter Site home page Giter Site logo

aqoviaelements / properties-from-ancestor-behavior Goto Github PK

View Code? Open in Web Editor NEW
2.0 9.0 1.0 99 KB

Polymer mixin (behavior) for a web component to take a property value from the closest ancestor that has it

HTML 100.00%
html-elements polymer polymer1 polymer2 polymer-elements web-components

properties-from-ancestor-behavior's Introduction

Published bower package version Build status Published on webcomponents.org

Demo and API docs

PropertiesFromAncestorBehavior

A Polymer Behavior for a web component to take a property value from the closest ancestor that has it. Supports change events.

Where it might be a good idea?

It can be useful for ubiquitous properties (think language of the UI, currency, disabled state to every input of a form, etc.) which would otherwise pollute attributes of every component with same boilerplate expression, or force usage of a global variable (usually without binding to changes).
An extra feature of being able to observe attributes helps with achieving easy-to-use web components, reusable in other app frameworks or pure HTML.

Usage:

Technically, it's a behavior factory function. To have a behavior instance created, you call the PropertiesFromAncestorBehavior().

class MyComponent extends Polymer.mixinBehaviors([
    PropertiesFromAncestorBehavior({
            // Just declaring a property here is enough to make it work.
            myProp1: {
                // Optionally, you can provide a default. If no ancestor is found, `defaultValue` will be used:
                defaultValue: 123, // (don't use the polymer's 'value:' for this though, because it may cause double initialization - once with such value, once with the value from ancestor (if they're different). That's because we can only reach the ancestor on 'attached', which happens after defaults get applied.

                // See "API Reference - Per-Property settings" for all available options

                // If you don't care about polylint, you can avoid repetition and just put here all other property settings. They get passed to declaration of this property on the element:
                notify: true,
                observer: '_myProp1Changed',
            },
        }) ], Polymer.Element)
{
    static get is() { return "my-component"; }
    static get properties() {
        return {
            // But if you want to keep polylint happy, you need to list the property here too:
            myProp1: {
                // Custom settings still work:
                notify: true,
                observer: '_myProp1Changed',
                // But you don't want to use 'value:' here but rather 'defaultValue:' above. See comment there for 'why'.
            }
        };
    }
}

the above declaration will make the properties available in <my-component>s template:

<template>
    myProp1 value is {{myProp1}}
    <br/>
    myProp2 value is {{myProp2}}
</template>

while their values will come from the closest ancestor that has the corresponding dash-case attribute.

<example-container-component my-prop1="some value" my-prop2="some other value"><!-- Container can also be a simple HTML <div>. As long as it has the attributes. These attributes are a requirement to discover the ancestor. If the element also has matching properties, they instead will be taken and their changes listened to. -->
  <my-component></my-component>
</example-container-component>

API Reference - Per-Property settings

These parameters can be specified for each property:

  • defaultValue (Optional)
    If no ancestor is found, defaultValue will be used.
  • ancestorMatches (Optional) of type HTML Selector
    • Instead of looking for ancestor with the dash-case attribute, you can provide a selector. This especially is needed for boolean attributes that start with 'false', because it's represented as no attribute at all. Example: ancestorMatches: '.ancestor-markup-class' - will listen to the closest ancestor with class="ancestor-markup-class"
  • ancestorObservedItem (Optional)
    of enum type
    PropertiesFromAncestorBehavior.ObservedItem.PROPERTY_CHANGED_EVENT (default)
    PropertiesFromAncestorBehavior.ObservedItem.ATTRIBUTE
    If the ancestor doesn't emit *-changed events, you can use ATTRIBUTE to tell the behavior that it should use a MutationObserver to listen to the attribute.
    Take note that in this case the handlers will not fire in the same thread as the change, so if you need to schedule some work after the handlers, you will need to use setTimeout(..., 0).
  • ancestorPropertyAlias (Optional)
    If the name of the property is different on the ancestor, specify it here (specify 'camelCase' name, but this will also change the expected dash-case attribute and '*-changed' event names).

HOW-TO: Automatically reflect container's lang attribute in your multi-language component

The HTML lang attribute is a standard way to declare the content language. It's useful for applying [lang=...] CSS rules and is recommended for future applications. It's then a perfect candidate to specify the language in one place and have your components automatically display the right contents:

<html lang="en">
  ...
  <p>
    <some-web-component>
      <span>
        <another-web-component></another-web-component>
      <span>
    </some-web-component>
  </p>
  ...
</html>

to achieve this effect with the Polymer's AppLocalizeBehavior, all you need to do is add our behavior next to it, with the following declaration:

...
class ... extends ...
    Polymer.AppLocalizeBehavior,
    PropertiesFromAncestorBehavior({
            language: {
                // This is needed because `AppLocalizeBehavior` requires `language` for the name - see also https://github.com/PolymerElements/app-localize-behavior/issues/98
                ancestorPropertyAlias: 'lang',
                // Observing the attribute will allow changing the language and seeing immediate effect:
                ancestorObservedItem: PropertiesFromAncestorBehavior.ObservedItem.ATTRIBUTE,
            },
        }),
...

HOW-TO: Native-like support for <fieldset disabled>

The native HTML form input elements like <input>, <button>, <select> elements don't require you to pass the disabled attribute to each usage. You use the ancestor fieldset disabled feature to disable all of the containing inputs.

Here's how this behavior can be used to support the same feature when your web component isn't built from these native inputs:

<template>
    <span on-tap="{{handleTap}}">Something only clickable when enabled</span>
</template>
...
class ... extends ...
    PropertiesFromAncestorBehavior({
            disabled: {
                // Use 'ancestorMatches' to provide a way to match while it's enabled (has no disabled attribute)
                ancestorMatches: 'fieldset',

                // <fieldset> has a disabled property, but changing it doesn't emit 'disabled-changed' event, so we must observe the attribute:
                ancestorObservedItem: PropertiesFromAncestorBehavior.ObservedItem.ATTRIBUTE,

                // Specify type, to properly get empty 'disabled' attribute deserialized into 'true':
                type: Boolean,
            },
        })
    ...
{
    ...
    handleTap() {
        if (this.disabled) return;
    
        // If we got here, we're enabled. Handle legitimate click:
        this.DoSomething();
    }
}

properties-from-ancestor-behavior's People

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

zlamma

properties-from-ancestor-behavior's Issues

Add support for upward data flow - changing the ancestor value by changing the property in descendant

We currently don't need this feature, so it might not get implemented - please react with 'thumbs up' here to give us feedback that you'd like it.

Since, during reading in descendant, we're already abstracting the fact that the property comes from the ancestor, we might as well not require a leaky abstraction to change the value.

This should be relatively easy to implement in web-component ancestors (which already fire 'my-prop1-changed' events), but will be more tricky for native DOM nodes (need to not fall into recursive call; need to respect the serialization rules using serializeValueToAttribute. Maybe we should also set a property, to save on serialization & subsequent deserialization in children.), but this variant might actually be pretty valuable - it would allow creating elements that are very dynamic, while looking like 'pure static HTML' structure (e.g. no JavaScript security concern or Polymer knowledge needed - just put your global app parameters in attributes of outermost elements and your web components inside just look like HTML).

Add support for receiving a boolean that is initially false (e.g. get notified about <fieldset disabled>)

Suggestion:
Allow to mimic the way native HTML control receive the ubiquitous disabled flag from ancestor <fieldset>:

<fieldset disabled>
    <input type="text" name="some-standard-textbox-control" />
    <select id="some-standard-select-control">...</select>
    <my-crazy-form-control1/>
    <my-crazy-form-control2/>
    <button id="some-standard-button-control" type="submit">Submit</button>
</fieldset>

(all the native controls will be disabled - note we did not have to pass any disabled arguments except for the ancestor).

Currently, receving the ancestor's disabled attribute will only work if the fieldset is disabled from the start (so has the disabled attribute).

To allow this to work where the fieldset initially has no such attribute and dynamically react to disabling inside the web controls, we can allow specifying a selector that allows to find the ancestor to bind:

PropertiesFromAncestorBehavior({
            disabled: {
                type: Boolean,
                ancestorMatches: 'fieldset',
            },
        })

and then listen to attribute changes on that ancestor (using MutationObserver) to receive the change to true.

This would make it easier to create components that match succinctness of the native HTML elements.

Add support for different attribute/property name in ancestor

Consider allowing:

PropertiesFromAncestorBehavior({
            language: {
                ancestorPropertyAlias: 'lang',
            },
        })

To allow boilerplate-less integration when some 3rd party behaviors that we want to use in our component impose a different attribute name than our ancestors already provide.

One fine example is HTML language specification. Adding this feature would allow PropertiesFromAncestorBehavior to be used to smoothly marry the HTML's blessed lang attribute/property with the Polymer's blessed AppLocalizeBehavior's language property.

(unless Polymer's component changes the property name - PolymerElements/app-localize-behavior#98)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.