DoneJS StealJS jQuery++ FuncUnit DocumentJS
6.0.1
5.33.2 4.3.0 3.14.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-deep-observable
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-compat
      • can-map-define
      • can-observable-array
      • can-observable-object
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Views
      • can-attribute-observable
      • can-component
      • can-observable-bindings
      • can-stache
      • can-stache-bindings
        • Syntaxes
          • on:event
          • key:raw
          • key:from
          • key:to
          • key:bind
      • can-stache-converters
      • can-stache-element
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-model
      • can-view-parser
      • can-view-scope
      • can-view-target
      • steal-stache
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-connect-ndjson
      • can-connect-tag
      • can-define-realtime-rest-model
      • can-define-rest-model
      • can-fixture
      • can-fixture-socket
      • can-local-store
      • can-memory-store
      • can-ndjson-stream
      • can-query-logic
      • can-realtime-rest-model
      • can-rest-model
      • can-set-legacy
      • can-super-model
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
      • can-string
      • can-string-to-any
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-type
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-stache-bindings

  • npm package badge
  • Star
  • Edit on GitHub

Listen to events and create one-way and two-way bindings.

Object

can-stache-bindings exports a binding object that can be added to can-stache via addBindings as follows:

import { stache, stacheBindings } from "can";

stache.addBindings(stacheBindings);

This is automatically done by can-stache-element and can-component, so these bindings are typically available automatically in can-stache.

Purpose

Bindings allow communication between html elements and observables (e.g. models, props, ViewModels, etc.).

Communication happens primarily by:

  • Listening to events and calling methods (<button on:click="this.doSomething()">)
  • Passing values (<input value:from="this.name">)

can-stache-bindings are designed to be:

  • Powerful — Many different types of binding behaviors are possible:
    • Pass data down and update when the data changes: <input value:from="this.name" />
    • Pass data up and update when the data changes: <input value:to="this.name" />
    • Pass data up and update on a specified event: <input on:input:value:to="this.name" />
    • Update both directions: <input value:bind="this.name" />
    • Listen to events and call a method: <input on:change="this.doSomething()" />
    • Listen to events and set a value: <input on:change="this.name = scope.element.value" />
  • Declarative — Instead of magic tags like (click) or {(key)}, it uses descriptive terms like on:, :from, :to, and :bind so beginners have an idea of what is happening.

can-stache-bindings is separate from can-stache as other view-binding syntaxes have been supported in the past.

Basic Use

The can-stache-bindings plugin provides useful custom attributes for template declarative events, one-way bindings, and two-way bindings on element attributes and the scope. Bindings communicate between two entities, typically a parent entity and a child entity. Bindings look like:

  • on:event="key()" for event binding.
  • prop:from="key" for one-way binding to a child.
  • prop:to="key" for one-way binding to a parent.
  • prop:bind="key" for two-way binding.

Note: DOM attribute names are case-insensitive, but props, ViewModel, or scope properties can be camelCase and stache will encode them so they work correctly in the DOM.

The following are the bindings available within can-stache:

  • event

    Binds to childEvent on <my-element> and calls method on the scope with the specified arguments:

    <my-element on:childEvent="method('primitive', key, hash1=key1)" />
    

    If the element is a native HTML element, binds to domEvent on the element and calls method on the scope with the specified arguments:

    <div on:domEvent="method('primitive', key, hash1=key1)" />
    

    You can also set a value. The following sets the todo.priority property to 1 when the button is clicked:

    <button on:click="todo.priority = 1">Critical</button>
    
  • one-way to child

    Updates childProp on <my-element> with value from the scope:

    <my-element childProp:from="value" />
    

    This can be read as “set childProp from value”.

    If the element is a native HTML element, updates the child-attr attribute or property of the element with value in the scope:

    <div child-attr:from="value" />
    

    Note: If the value being passed to the element is an object, changes to the object’s properties will still be visible to the element. Objects are passed by reference. See One-Way Binding With Objects.

Register can-stache-bindings

If you are not using can-stache-element or can-component, you can use can-stache-bindings in your templates by importing the stacheBindings module and registering it with addBindings like so:

import { stache, stacheBindings } from "can";

stache.addBindings(stacheBindings);

Binding types

The following are the bindings that should be used with can-stache:

  • one-way to parent

    Updates value in the scope with childProp in <my-element>:

    <my-element childProp:to="value" />
    

    This can be read as "send childProp to value".

    If the element is a native HTML element, it updates value in the scope with the childAttr attribute or property of the element.

    <div childAttr:to="value" />
    

    Note: If the value being passed to the element is an object, changes to the object’s properties will still be visible to the element. Objects are passed by reference. See One-Way Binding With Objects.

  • two-way

    Updates childProp in <my-element> with value in the scope and vice versa:

    <my-element childProp:bind="value" />
    

    Updates the childAttr attribute or property of the element with value in the scope and vice versa:

    <div childAttr:bind="value" />
    

Call a function when an event happens on an element

Use on:event to listen to when an event is dispatched on an element. The following calls the sayHi method when the button is clicked:

<say-hi></say-hi>
<script type="module">
import { StacheElement } from "can";

class SayHi extends StacheElement {
    static view = `<button on:click="this.sayHi()">Say Hi</button>`;

    sayHi() {
        alert("Hi!");
    }
}
customElements.define("say-hi", SayHi);
</script>

The event, element, and arguments the event handler would be called with are available via scope. The following prevents the form from being submitted by passing scope.event:

<my-demo></my-demo>
<script type="module">
import { ObservableArray, StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <form on:submit="this.reportData(scope.element, scope.event)">
            <input name="name" placeholder="name" />
            <input name="age" placeholder="age" />
            <button>Submit</button>
        </form>
        <h2>Data</h2>
        <ul>
            {{# for(submission of this.submissions) }}
                <li>{{ submission }}</li>
            {{/ for }}
        </ul>
    `;

    static props = {
        submissions: {
            get default() {
                return new ObservableArray();
            }
        }
    };

    reportData(form, submitEvent) {
        submitEvent.preventDefault();
        const submission = JSON.stringify({
            name: form.name.value,
            age: form.age.value
        });
        this.submissions.push(submission);
    }
}
customElements.define("my-demo", MyDemo);
</script>

Call a function when an event is dispatched from a component

Use on:event to listen to when an event is dispatched on a can-stache-element.

In the following example, <my-demo> listens to number events from <random-number-generator>:

<my-demo></my-demo>
<script type="module">
import { ObservableArray, StacheElement } from "can";

class RandomNumberGenerator extends StacheElement {
    connected() {
        const interval = setInterval( () => {
            this.dispatch({ type: "number", value: Math.random() });
        }, 1000);

        return () => {
            clearInterval(interval);
        };
    }
}

customElements.define("random-number-generator", RandomNumberGenerator);

class MyDemo extends StacheElement {
    static view = `
        <random-number-generator on:number="this.addNumber(scope.event.value)" />
        <h2>Numbers</h2>
        <ul>
            {{# for(number of this.numbers) }}
                <li>{{ number }}</li>
            {{/ for }}
        </ul>
    `;

    static props = {
        numbers: {
            get default() {
                return new ObservableArray();
            }
        }
    };

    addNumber(number) {
        this.numbers.push(number);
    }
}
customElements.define("my-demo", MyDemo);
</script>

Note that when properties are set on a can-stache-element, these produce events too. In the following example, <my-demo> listens to number produced when <random-number-generator>’s number property changes:

<my-demo></my-demo>
<script type="module">
import { ObservableArray, StacheElement } from "can";

class RandomNumberGenerator extends StacheElement {
    static props = {
        number: {
            value({ resolve }) {
                const interval = setInterval( () => {
                    resolve(Math.random())
                }, 1000);

                return () => {
                    clearInterval(interval);
                };
            }
        }
    };
}

customElements.define("random-number-generator", RandomNumberGenerator);

class MyDemo extends StacheElement {
    static view = `
        <random-number-generator on:number="this.addNumber(scope.viewModel.number)" />
        <h2>Numbers</h2>
        <ul>
            {{# for(number of this.numbers) }}
                <li>{{ number }}</li>
            {{/ for }}
        </ul>
    `;

    static props = {
        numbers: {
            get default() {
                return new ObservableArray();
            }
        }
    };

    addNumber(number) {
        this.numbers.push(number);
    }
}
customElements.define("my-demo", MyDemo);
</script>

Call a function when an event happens on a value in the scope (animation)

Use on:event:by:value to listen to an event and call a method. This can often be useful for running animations.

The following listens to when a todo’s complete event is fired and calls this.shake. this.shake uses anime to animate the <div>:

<my-demo></my-demo>
<script src="//unpkg.com/animejs@3/lib/anime.min.js"></script>
<script type="module">
import { ObservableObject, StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        {{# for(todo of this.todos) }}
            <div on:complete:by:todo="this.shake(scope.element)">
                <input type="checkbox" checked:bind="todo.complete" />
                {{ todo.name }}
            </div>
        {{/ for }}
    `;

    static props = {
        todos: {
            get default() {
                return [
                    new ObservableObject({ name: "animate", complete: false }),
                    new ObservableObject({ name: "celebrate", complete: true })
                ];
            }
        }
    };

    shake(element) {
        anime({
            targets: element,
            translateX: [ 10, -10, 0 ],
            easing: "linear"
        });
    }
}
customElements.define("my-demo", MyDemo);
</script>

Update an element’s value from the scope

Use key:from to:

  • initialize an element’s property or attribute with the value from stache’s scope, and
  • update the element’s property or attribute with the scope value changes.

The following shows updating the BIG RED BUTTON’s disabled from this.enabled in the scope. The not helper is used to inverse the value of this.enabled. Notice that as this.enabled changes, disabled updates.

<my-demo></my-demo>
<style>
.big-red {
    background-color: red; color: white;
    display: block; width: 100%; height: 50vh;
    cursor: pointer;
}
.big-red:disabled {
    background-color: #800000;
    color: black; cursor: auto;
}
</style>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <button on:click="this.enabled = true">Enable</button>
        <button on:click="this.enabled = false">Disable</button>

        <button
            disabled:from="not(this.enabled)"
            on:click="this.boom()"
            class="big-red">BIG RED BUTTON</button>
    `;

    static props = {
        enabled: false
    };

    boom() {
        alert("Red Alert!");
    }
}
customElements.define("my-demo", MyDemo);
</script>

Update a component’s value from the scope

Use key:from to:

  • initialize a can-stache-element’s property value from stache’s scope, and
  • update the property when the scope value changes.

The following

<my-demo></my-demo>
<style>
percentage-slider {
    border: solid 1px black;
    width: 100px; height: 20px;
    display: inline-block;
}
.percent { background-color: red; height: 20px; }
</style>
<script type="module">
import { StacheElement } from "can";

class PercentageSlider extends StacheElement {
    static view = `<div class="percent" style="width: {{ this.percent }}%"></div>`;

    static props = {
        percent: Number
    };
}

customElements.define("percentage-slider", PercentageSlider);

class MyDemo extends StacheElement {
    static view = `
        Percent Complete: <br/>
        <percentage-slider percent:from="this.value" />
        <br/>
        <button on:click="this.increase(-5)">-5</button>
        <button on:click="this.increase(5)">+5</button>
    `;

    static props = {
        value: 50
    };

    increase(amount) {
        const newValue = this.value + amount;
        if(newValue >= 0 && newValue <= 100) {
            this.value += amount;
        }
    }
}
customElements.define("my-demo", MyDemo);
</script>

key:from can be used to pass the results of functions like percent:from="this.method()".

Pass a value from an element to the scope

Use key:to to pass a value from an element to a value on the scope.

The following updates name on the can-stache-element when the <input>’s change event fires:

<my-demo></my-demo>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <p>Name: {{ this.name }}</p>
        <p>Update name when “change” fires: <input value:to="this.name" /></p>
    `;

    static props = {
        name: String
    };
}
customElements.define("my-demo", MyDemo);
</script>

The element value will be read immediately and used to set the scope value. The following shows that the default name will be overwritten to be an empty string because the input’s value is read and overwrites the scope value:

<my-demo></my-demo>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <p>Name: {{ this.name }}</p>
        <p>Update name when “change” fires: <input value:to="this.name" /></p>
    `;

    static props = {
        name: "Justin"
    };
}
customElements.define("my-demo", MyDemo);
</script>

Use on:event:elementPropery:to to customize which event to listen to. The following switches to the input event:

<my-demo></my-demo>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <p>Name: {{ this.name }}</p>
        <p>Update name as you type: <input on:input:value:to="this.name" /></p>
    `;

    static props = {
        name: "Justin"
    };
}
customElements.define("my-demo", MyDemo);
</script>

NOTE: Using on:event:elementPropery:to prevents initialization of the value until an event happens. You’ll notice the name is left as "Justin" until you start typing.

Pass an element to the scope

You can use this:to="key" to pass an element reference to a value on the scope.

The following sets the video element as the video property so play() can be called when isPlaying is set to true:

<video-player src="https://bit.ly/can-tom-n-jerry"></video-player>
<script type="module">
import { fromAttribute, StacheElement } from "can";

class VideoPlayer extends StacheElement {
    static view = `
        <video this:to="this.video">
            <source src="{{ this.src }}" />
        </video>
        <button on:click="this.togglePlay()">
            {{# if(this.isPlaying) }} Pause {{ else }} Play {{/ if }}
        </button>
    `;

    static props = {
        isPlaying: Boolean,
        src: {
          bind: fromAttribute,
          type: String
        },
        video: HTMLVideoElement
    };

    connected() {
        this.listenTo("isPlaying", ({ value }) => {
            if (value) {
                this.video.play();
            } else {
                this.video.pause();
            }
        });
    }

    togglePlay() {
        this.isPlaying = !this.isPlaying;
    }
}
customElements.define("video-player", VideoPlayer);
</script>

Pass a value from a component to the scope

Use key:to to pass a value from a component to a value on the scope.

The following uses passes random numbers from <random-number-generator> to <my-demo> using number:to=""

<my-demo></my-demo>
<script type="module">
import { StacheElement, type } from "can";

class RandomNumberGenerator extends StacheElement {
    static props = {
        number: {
            value({ resolve }) {
                const interval = setInterval( () => {
                    resolve(Math.random());
                }, 1000);
                return () => {
                    clearInterval(interval);
                };
            }
        }
    };
}

customElements.define("random-number-generator", RandomNumberGenerator);

class MyDemo extends StacheElement {
    static view = `
        <random-number-generator number:to="this.randomNumber" />
        <h1>Your random number is {{ this.randomNumber }}</h1>
    `;

    static props = {
        randomNumber: type.maybeConvert(Number)
    };
}
customElements.define("my-demo", MyDemo);
</script>

NOTE: Just like passing an element value to the scope, passing a property value will overwrite existing scope values. You can use on:event:key:to="scopeValue" to specify the event to listen to.

Keep a parent and child in sync

Use key:bind to keep a parent and child value in sync. Use key:bind to keep an element’s value in sync with a scope value.

The following keeps an <input>’s .value in sync with this.name in the scope:

<my-demo></my-demo>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <p>Name is currently: {{ this.name }}</p>
        <p><input value:bind="this.name" /></p>
    `;

    static props = {
        name: "Katherine Johnson"
    };
}
customElements.define("my-demo", MyDemo);
</script>

Use on:event:key:bind="scopeValue" to specify the event that should cause the scope value to update. The following updates this.name when the <input>’s input event fires:

<my-demo></my-demo>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <p>Name is currently: {{ this.name }}</p>
        <p><input on:input:value:bind="this.name" /></p>
    `;

    static props = {
        name: "Dorothy Vaughan"
    };
}
customElements.define("my-demo", MyDemo);
</script>

NOTE: key:bind always initializes parent and child values to match, even if on:event:key:bind="scopeKey" is used to specify the type of event. Read more about initialization on key:bind.

The following keeps props in sync with a scope value:

<my-demo></my-demo>
<script type="module">
import { StacheElement, type } from "can";

class NameEditor extends StacheElement {
    static view = `
        <input placeholder="first" value:bind="first" />
        <input placeholder="last" value:bind="last" />
    `;

    static props = {
        first: String,
        last: String,
        get fullName() {
            return this.first + " " + this.last;
        },
        set fullName(newVal) {
            const parts = newVal.split(" ");
            this.first = parts[0] || "";
            this.last = parts[1] || "";
        }
    };
}

customElements.define("name-editor", NameEditor);

class MyDemo extends StacheElement {
    static view = `
        <p>Name is currently: {{ this.name }}</p>
        <p><name-editor fullName:bind="this.name" /></p>
        <p><button on:click="this.name = 'Captain Marvel'">Set name as Captain Marvel</button>
    `;

    static props = {
        name: "Carol Danvers"
    };
}
customElements.define("my-demo", MyDemo);
</script>

Other Uses

The following are some advanced or non-obvious use cases.

Pass values between siblings

Sometimes you have two sibling elements that need to communicate and creating a value in the parent element is unnecessary. Use let to create a variable that gets passed between both elements. The following creates an editing variable that is used to communicate between <my-drivers> and <edit-plate>:

<my-demo></my-demo>
<script type="module">
import { ObservableObject, StacheElement, type } from "can";

class MyDrivers extends StacheElement {
    static view = `
        <p>Select a driver:</p>
        <ul>
            {{# for(driver of this.drivers) }}
                <li on:click="this.selected = driver">
                    {{ driver.title }} {{ driver.first }} {{ driver.last }} - {{ driver.licensePlate }}
                </li>
            {{/ for }}
        </ul>
    `;

    static props = {
        drivers: {
            get default() {
                return [
                    new ObservableObject({ title: "Dr.", first: "Cosmo", last: "Kramer", licensePlate: "543210" }),
                    new ObservableObject({ title: "Ms.", first: "Elaine", last: "Benes", licensePlate: "621433" })
                ];
            }
        },
        selected: type.Any
    };
}

customElements.define("my-drivers", MyDrivers);

class EditPlate extends StacheElement {
    static view = `<input on:input="this.plateName = scope.element.value" value:from="this.plateName" />`;

    static props = {
        plateName: String
    };
}

customElements.define("edit-plate", EditPlate);

class MyDemo extends StacheElement {
    static view = `
        {{ let editing=undefined }}
        <my-drivers selected:to="editing" />
        {{# if(editing) }}
            <edit-plate plateName:bind="editing.licensePlate" />
        {{/ if }}
    `;
}
customElements.define("my-demo", MyDemo);
</script>

Call a function when a custom event happens on an element

Custom events can be a great way to simplify complex DOM interactions. on:event listens to:

  • Custom events dispatched by the browser (element.dispatchEvent(event))
  • Custom events registered by can-dom-events.

See an example of dispatching custom events.

The following example shows a <in-view> component that dispatches a inview custom event on elements when they scroll into view. <my-demo> listens to those events and loads data with <div on:inview="this.getData(item)">.

<my-demo></my-demo>
<style>
in-view {
    border: solid 1px black;
    display: block;
    height: 90vh;
    overflow: auto;
}
</style>
<script type="module">
import { ObservableObject, StacheElement } from "can";

const isVisibleSymbol = Symbol("isVisible");

class InView extends StacheElement {
    static view = `{{ content(context) }}`;

    connected() {
        function dispatchEvents() {
            // Get all visible elements
            const visible = Array.from(this.childNodes).filter( child => {
                return child.offsetTop > this.scrollTop
                    && child.offsetTop <= this.scrollTop + this.clientHeight;
            });
            // dispatch event on elements that have not
            // been dispatched
            visible.forEach( child => {
                if (!child[isVisibleSymbol]) {
                    child[isVisibleSymbol] = true;
                    child.dispatchEvent(new Event("inview"));
                }
            });
        }
        // Dispatch on visible elements right away
        dispatchEvents.call(this);
        // On scroll, dispatch
        this.listenTo(this, "scroll", dispatchEvents);
    }
}
customElements.define("in-view", InView);

class MyDemo extends StacheElement {
    static view = `
        {{< viewContent }}
            {{# for(item of this.items) }}
                <div on:inview="this.getData(item)">
                    {{ item.data }}
                </div>
            {{/ for }}
        {{/ viewContent }}
        <in-view content:from="viewContent" context:from="this" />
    `;

    static props = {
        items: {
            get default() {
                const items = [];
                for (let i = 0; i < 400; i++) {
                    items.push(new ObservableObject({ data: "unloaded" }));
                }
                return items;
            }
        }
    };

    getData(item) {
        item.data = "loading…"
        setTimeout(() => {
            item.data = "loaded";
        }, Math.random() * 1000);
    }
}
customElements.define("my-demo", MyDemo);
</script>

See an example of using custom events.

CanJS has a special event registry - can-dom-events. You can add custom events to to this registry and listen to those events with on:event.

CanJS already has several custom events:

  • domMutateEvents - Listen to when an element is inserted or removed.
  • can-event-dom-enter - Listen to when the Enter key is pressed.

The following adds the enter and inserted events into the global registry and uses them:

<my-demo></my-demo>
<script src="//unpkg.com/animejs@3/lib/anime.min.js"></script>
<style>
.light {position: relative; left: 20px; width: 100px; height: 100px;}
.red {background-color: red;}
.green {background-color: green;}
.yellow {background-color: yellow;}
</style>
<script type="module">
import { domEvents, domMutateDomEvents, enterEvent, StacheElement } from "can/ecosystem";

domEvents.addEvent(enterEvent);
domEvents.addEvent(domMutateDomEvents.inserted);

class MyDemo extends StacheElement {
    static view = `
        <div class="container" tabindex="0"
            on:enter="this.nextState()">
            Click me and hit enter.
            {{# switch(this.state) }}
                    {{# case("red") }}
                            <div class="light red"
                                on:inserted="this.shake(scope.element)">Red Light</div>
                    {{/ case }}
                    {{# case("yellow") }}
                            <div class="light yellow"
                                on:inserted="this.shake(scope.element)">Yellow Light</div>
                    {{/ case }}
                    {{# case("green") }}
                            <div class="light green"
                                on:inserted="this.shake(scope.element)">Green Light</div>
                    {{/ case }}
            {{/ switch }}
        </div>
    `;

    static props = {
        state: "red"
    };

    nextState() {
        const states = { red: "yellow", yellow: "green", green: "red" };
        this.state = states[this.state];
    }

    shake(element) {
        anime({
            targets: element,
            translateX: [ 10, -10, 0 ],
            easing: "linear"
        });
    }
}
customElements.define("my-demo", MyDemo);
</script>

Using converters

Converters allow you to setup two-way translations between child and parent values. These work great with key:to and key:bind bindings.

For example, not can be used to update a scope value with the opposite of the element’s checked property:

<my-demo></my-demo>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <label>
            <input type="checkbox" checked:bind="not(this.activated)" />
            Disable
        </label>
    `;

    static props = {
        activated: true
    };
}
customElements.define("my-demo", MyDemo);
</script>

not comes with can-stache, however can-stache-converters has a bunch of other useful converters. You can also create your own converters with addConverter.

Binding to custom attributes (focused and values)

can-attribute-observable creates observables used for binding element properties and attributes.

<my-demo></my-demo>
<style>
:focus { background-color: yellow; }
</style>
<script type="module">
import { StacheElement } from "can";

class MyDemo extends StacheElement {
    static view = `
        <input
            on:input:value:bind="this.cardNumber"
            placeholder="Card Number (9 digits)" />
        <input size="4"
            on:input:value:bind="this.cvcNumber"
            focused:from="this.cvcFocus"
            on:blur="this.dispatch('cvcBlur')"
            placeholder="CVC" />
        <button
            focused:from="this.payFocus"
            on:blur="this.dispatch('payBlur')">Pay</button>
    `;

    static props = {
        cardNumber: String,
        cvcFocus: {
            value({ listenTo, resolve }) {
                listenTo("cardNumber", ({ value }) => {
                    if (value.length === 9) {
                        resolve(true);
                    } else {
                        resolve(false);
                    }
                });
                listenTo("cvcBlur", () => {
                    resolve(false);
                });
            }
        },
        cvcNumber: String,
        payFocus: {
            value({ listenTo, resolve }) {
                listenTo("cvcNumber", ({ value }) => {
                    if (value.length === 3) {
                        resolve(true);
                    } else {
                        resolve(false);
                    }
                });
                listenTo("payBlur", () => {
                    resolve(false);
                });
            }
        }
    };
}
customElements.define("my-demo", MyDemo);
</script>

Read can-attribute-observable for a values example with <select multiple>.

Bindings with objects

All of the bindings pass single references between parent and child values. This means that objects that are passed are passed as-is, they are not cloned or copied in anyway. And this means that changes to an object might be visible to either parent or child.

The following shows passing a name object and changes to that object’s first and last are visible to the <my-demo> component:

<my-demo></my-demo>
<script type="module">
import { ObservableObject, StacheElement } from "can";

class NameEditor extends StacheElement {
    static view = `
        <input on:input:value:bind="this.name.first" />
        <input on:input:value:bind="this.name.last" />
    `;

    static props = {
        name: ObservableObject
    };
}
customElements.define("name-editor", NameEditor);

class MyDemo extends StacheElement {
    static view = `
        <p>First: {{ this.name.first }}, Last: {{ this.name.last }}</p>
        <name-editor name:from="this.name" />
    `;

    static props = {
        name: {
            get default() {
                return new ObservableObject({ first: "Justin", last: "Meyer" });
            }
        }
    };
}
customElements.define("my-demo", MyDemo);
</script>

Sticky Bindings

key:bind bindings are sticky. This means that if a child value updates a parent value and the parent and child value do not match, the parent value will be used to update the child an additional time.

In the following example, <parent-element> always ensures that parentName is upper-cased. If you type lower-case characters in the input (example: foo bar), you’ll see that Parent Name, Child Name, and the input’s value are made upper-cased.

<parent-element></parent-element>
<script type="module">
import { StacheElement, type } from "can";

class ChildComponent extends StacheElement {
    static view = `
        <p>Child Name: {{ this.childName }}</p>
        <input value:bind="this.childName" />
    `;

    static props = {
        childName: type.Any
    };
}

customElements.define("child-element", ChildComponent);

class ParentComponent extends StacheElement {
    static view = `
        <p>Parent Name: {{ this.parentName }}</p>
        <child-element childName:bind="this.parentName" />
    `;

    static props = {
        parentName: {
            default: "JUSTIN MEYER",
            set(newVal) {
                return newVal.toUpperCase();
            }
        }
    };
}
customElements.define("parent-element", ParentComponent);
</script>

This happens because after parentName is set, can-bind compares parentName’s 'FOO BAR to childName’s foo bar. Because they are not equal, childName is set to FOO BAR. Setting childName to FOO BAR will also set the <input> to FOO BAR.

How it works

Custom attributes are registered with can-view-callbacks. can-stache will call back these handlers as it encounters these attributes.

For data bindings:

  1. When those callbacks are encountered, an observable value is setup for both sides of the binding. For example, keyA:bind="keyB" will create an observable representing the keyA value and an observable representing the keyB value.
  2. Those observables are passed to can-bind which is used to update one value when the other value changes.

For component data bindings:

  1. When a component is created, it processes all the binding attributes at the same time and it figures out the right-hand (scope) values first. This is so can-stache-element can create its properties with the values in the scope. This avoids unnecessary changes and improves performance.

For event bindings:

  1. It parses the binding and attaches an event listener. When that event listener is called, it parses the right-hand expression and runs it.

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 6.0.1.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news