can-stache-bindings
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" />
- Pass data down and update when the data changes:
- 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:
-
Binds to
childEvent
on<my-element>
and callsmethod
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 callsmethod
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 to1
when the button is clicked:<button on:click="todo.priority = 1">Critical</button>
-
Updates
childProp
on<my-element>
withvalue
from the scope:<my-element childProp:from="value" />
This can be read as “set
childProp
fromvalue
”.If the element is a native HTML element, updates the
child-attr
attribute or property of the element withvalue
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:
-
Updates
value
in the scope withchildProp
in<my-element>
:<my-element childProp:to="value" />
This can be read as "send
childProp
tovalue
".If the element is a native HTML element, it updates
value
in the scope with thechildAttr
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.
-
Updates
childProp
in<my-element>
withvalue
in the scope and vice versa:<my-element childProp:bind="value" />
Updates the
childAttr
attribute or property of the element withvalue
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 thename
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:
- 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 thekeyA
value and an observable representing thekeyB
value. - Those observables are passed to can-bind which is used to update one value when the other value changes.
For component data bindings:
- 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:
- It parses the binding and attaches an event listener. When that event listener is called, it parses the right-hand expression and runs it.