can-stache-bindings
Version:
Default binding syntaxes for can-stache
1,264 lines (1,012 loc) • 36.2 kB
Markdown
@module {Object} can-stache-bindings
@parent can-views
@collection can-core
@group can-stache-bindings.syntaxes Syntaxes
@package ../package.json
@outline 2
Listen to events and create one-way and two-way bindings.
@type {Object}
[can-stache-bindings] exports a binding object that can be added to [can-stache]
via [can-stache.addBindings] as follows:
```js
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].
@body
## Purpose
Bindings allow communication between html elements
and observables (e.g. [can-rest-model models], [can-stache-element/static.props], [can-component.prototype.ViewModel 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 [can-stache-bindings.event on:], [can-stache-bindings.toChild :from], [can-stache-bindings.toParent :to], and [can-stache-bindings.twoWay :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 [can-view-callbacks.attr custom attributes] for template declarative events, one-way bindings, and two-way
bindings on element attributes and the [can-view-scope scope].
Bindings communicate between two entities, typically a __parent__
entity and a __child__ entity. Bindings look like:
- [can-stache-bindings.event on:event="key()"] for event binding.
- [can-stache-bindings.toChild prop:from="key"] for one-way binding to a child.
- [can-stache-bindings.toParent prop:to="key"] for one-way binding to a parent.
- [can-stache-bindings.twoWay prop:bind="key"] for two-way binding.
> __Note:__ DOM attribute names are case-insensitive, but [can-stache-element/static.props], [can-component.prototype.ViewModel], or [can-view-scope scope] properties can be `camelCase` and [can-stache stache] will encode them so they work correctly in the DOM.
The following are the bindings available within [can-stache]:
- __[can-stache-bindings.event event]__
Binds to `childEvent` on `<my-element>` and calls
`method` on the [can-view-scope scope] with the specified arguments:
```html
<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 [can-view-scope scope] with the specified arguments:
```html
<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:
```html
<button on:click="todo.priority = 1">Critical</button>
```
- __[can-stache-bindings.toChild one-way to child]__
Updates `childProp` on `<my-element>` with `value` from the [can-view-scope scope]:
```html
<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 [can-view-scope scope]:
```html
<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 [can-stache-bindings#OneWayBindingWithObjects 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 [can-stache.addBindings] like so:
```js
import { stache, stacheBindings } from "can";
stache.addBindings(stacheBindings);
```
## Binding types
The following are the bindings that should be used with [can-stache]:
- __[can-stache-bindings.toParent one-way to parent]__
Updates `value` in the [can-view-scope scope] with `childProp`
in `<my-element>`:
```html
<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 [can-view-scope scope] with the `childAttr` attribute or property of the element.
```html
<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 [can-stache-bindings#OneWayBindingWithObjects One-Way Binding With Objects].
- __[can-stache-bindings.twoWay two-way]__
Updates `childProp` in `<my-element>` with `value` in the [can-view-scope scope] and vice versa:
```html
<my-element childProp:bind="value" />
```
Updates the `childAttr` attribute or property of the element with `value`
in the [can-view-scope scope] and vice versa:
```html
<div childAttr:bind="value" />
```
### Call a function when an event happens on an element
Use [can-stache-bindings.event] to listen to when an event is dispatched on
an element. The following calls the `sayHi` method when the
button is clicked:
```html
<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>
```
@codepen
@highlight 6-10,only
The event, element, and arguments the event handler would be called with are available
via [can-stache/keys/scope]. The following prevents the form from being submitted
by passing `scope.event`:
```html
<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>
```
@codepen
@highlight 7,29,only
### Call a function when an event is dispatched from a component
Use [can-stache-bindings.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>`:
```html
<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>
```
@codepen
@highlight 8,21,only
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 [can-observable-object/define/value changes]:
```html
<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>
```
@codepen
@highlight 7-17,25,only
### 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](https://animejs.com/) to animate the `<div>`:
```html
<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>
```
@codepen
@highlight 9,27-33,only
### Update an element’s value from the scope
Use [can-stache-bindings.toChild] to:
- initialize an element’s property or attribute with the
value from [can-stache stache’s] [can-view-scope 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 [can-stache.helpers.not] helper
is used to inverse the value of `this.enabled`. Notice that as `this.enabled`
changes, `disabled` updates.
```html
<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>
```
@highlight 22,only
@codepen
### Update a component’s value from the scope
Use [can-stache-bindings.toChild] to:
- initialize a [can-stache-element]’s property value from [can-stache stache’s] [can-view-scope scope], and
- update the property when the scope value changes.
The following
```html
<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>
```
@highlight 28,35,41,only
@codepen
[can-stache-bindings.toChild] can be used to pass the results of functions like `percent:from="this.method()"`.
### Pass a value from an element to the scope
Use [can-stache-bindings.toParent] 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:
```html
<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>
```
@highlight 8,only
@codepen
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:
```html
<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>
```
@highlight 8,12,only
@codepen
Use `on:event:elementPropery:to` to customize which event to listen to. The following
switches to the `input` event:
```html
<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>
```
@highlight 8,only
@codepen
> 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()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) can be called when `isPlaying` is set to true:
```html
<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>
```
@highlight 7,21,27,only
@codepen
### Pass a value from a component to the scope
Use [can-stache-bindings.toParent] 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=""`
```html
<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>
```
@highlight 24,only
@codepen
> 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 [can-stache-bindings.twoWay] to keep a parent and child value in sync. Use [can-stache-bindings.twoWay]
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:
```html
<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>
```
@highlight 8,only
@codepen
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:
```html
<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>
```
@highlight 8,only
@codepen
> NOTE: [can-stache-bindings.twoWay] 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 [can-stache-bindings.twoWay].
The following keeps [can-stache-element/static.props] in sync with a
scope value:
```html
<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>
```
@highlight 30,only
@codepen
## 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 [can-stache.helpers.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>`:
```html
<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>
```
@highlight 10,44-48,only
@codepen
### Call a function when a custom event happens on an element
Custom events can be a great way to simplify complex DOM interactions.
[can-stache-bindings.event] listens to:
- Custom events dispatched by the browser (`element.dispatchEvent(event)`)
- Custom events registered by [can-dom-events].
<details>
<summary>
See an example of dispatching custom events.
</summary>
The following example shows a `<in-view>` component that dispatches a `inview` [custom event](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events) on elements when
they scroll into view. `<my-demo>` listens to those events and loads data with `<div on:inview="this.getData(item)">`.
```html
<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>
```
@codepen
@highlight 30,46,only
</details>
<details>
<summary>
See an example of using custom events.
</summary>
CanJS has a special event registry - [can-dom-events]. You can add custom events to to this registry and
listen to those events with [can-stache-bindings.event].
CanJS already has several custom events:
- [can-dom-mutate/events/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:
```html
<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>
```
@codepen
@highlight 10-13,18,23,27,31,only
</details>
### Using converters
Converters allow you to setup two-way translations between __child__ and __parent__ values. These work
great with [can-stache-bindings.toParent] and [can-stache-bindings.twoWay] bindings.
For example, [can-stache.helpers.not] can be used to update a scope value with the opposite of the
element’s `checked` property:
```html
<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>
```
@codepen
@highlight 8,only
[can-stache.helpers.not] comes with [can-stache], however [can-stache-converters] has a bunch of
other useful converters. You can also create your own converters with [can-stache.addConverter].
### Binding to custom attributes (focused and values)
[can-attribute-observable] creates observables used for binding
element properties and attributes.
```html
<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>
```
@codepen
@highlight 15,19,25-38,40-53,only
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:
```html
<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>
```
@codepen
@highlight 7-8,19-20,only
### Sticky Bindings
[can-stache-bindings.twoWay] 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.
```html
<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>
```
@codepen
@highlight 7-8,20-21,27-29,only
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.