can-stache-bindings
Advanced tools
Comparing version 4.7.2 to 4.7.3
@@ -721,3 +721,3 @@ "use strict"; | ||
attribute: function(el, scope, prop, bindingData, mustBeGettable, stickyCompute, event, bindingInfo) { | ||
return new AttributeObservable(el, prop, bindingData, event); | ||
return new AttributeObservable(el, prop, {}, event); | ||
} | ||
@@ -724,0 +724,0 @@ }; |
1227
docs/bindings.md
@@ -1,2 +0,2 @@ | ||
@module can-stache-bindings | ||
@module {Object} can-stache-bindings | ||
@parent can-views | ||
@@ -6,11 +6,54 @@ @collection can-core | ||
@package ../package.json | ||
@outline 2 | ||
Provides template events, one-way bindings, and two-way bindings. | ||
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-component]. So these bindings are | ||
typically available automatically in [can-stache]. | ||
@body | ||
## Use | ||
## Purpose | ||
Bindings allow communication between html elements | ||
and observables like [can-component.prototype.ViewModel ViewModels] and | ||
[can-rest-model models]. | ||
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 keep updating: `<input value:from="this.name"/>` | ||
- Pass data up and keep updating: `<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 `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, component [can-component::ViewModel ViewModels], and the [can-view-scope scope]. Bindings look like: | ||
bindings on element attributes, component [can-component::ViewModel ViewModels], and the [can-view-scope scope]. | ||
Bindings communicate between two entities, typically a __parent__ | ||
entity and a __child__ entity. Bindings look like: | ||
@@ -27,101 +70,1181 @@ | ||
#### [can-stache-bindings.event event] | ||
- __[can-stache-bindings.event event]__ | ||
Binds to `childEvent` on `<my-component>`'s [can-component::ViewModel ViewModel] and calls | ||
`method` on the [can-view-scope scope] with the specified arguments: | ||
Binds to `childEvent` on `<my-component>`'s [can-component::ViewModel ViewModel] and calls | ||
`method` on the [can-view-scope scope] with the specified arguments: | ||
```html | ||
<my-component on:childEvent="method('primitive', key, hash1=key1)"/> | ||
``` | ||
If the element does not have a [can-component::ViewModel ViewModel], 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` in `<my-component>`’s [can-component::ViewModel ViewModel] with `value` in the [can-view-scope scope]: | ||
```html | ||
<my-component childProp:from="value"/> | ||
``` | ||
> This can be read as "set `childProp` _from_ `value`". | ||
If the element does not have a [can-component::ViewModel ViewModel], 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 component is an object, changes to the object's properties will still be visible to the component. Objects are passed by reference. See [can-stache-bindings#OneWayBindingWithObjects One Way Binding With Objects]. | ||
- __[can-stache-bindings.toParent one-way to parent]__ | ||
Updates `value` in the [can-view-scope scope] with `childProp` | ||
in `<my-component>`’s [can-component::ViewModel ViewModel]: | ||
```html | ||
<my-component childProp:to="value"/> | ||
``` | ||
> This can be read as "send `childProp` _to_ `value`". | ||
If the element does not have a [can-component::ViewModel ViewModel], 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 component is an object, changes to the object's properties will still be visible to the component. 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-component>`’s [can-component::ViewModel ViewModel] with `value` in the [can-view-scope scope] and vice versa: | ||
```html | ||
<my-component 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 `ViewModel`'s `sayHi` method when the | ||
button is clicked: | ||
```html | ||
<my-component on:childEvent="method('primitive', key, hash1=key1)"/> | ||
<say-hi></say-hi> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-counter", | ||
view: ` | ||
<button on:click="this.sayHi()">Say Hi</button> | ||
`, | ||
ViewModel: { | ||
sayHi(){ | ||
alert("Hi!"); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
If the element does not have a [can-component::ViewModel ViewModel], binds to `domEvent` on the element and calls | ||
`method` on the [can-view-scope scope] with the specified arguments: | ||
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 | ||
<div on:domEvent="method('primitive', key, hash1=key1)"/> | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
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(data of this.submissions) }} | ||
<li>{{data}}</li> | ||
{{/for}} | ||
</ul> | ||
`, | ||
ViewModel: { | ||
submissions: {default: () => []}, | ||
reportData(form, submitEvent){ | ||
submitEvent.preventDefault(); | ||
var data = JSON.stringify({ | ||
name: form.name.value, | ||
age: form.age.value | ||
}); | ||
this.submissions.push( data ); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
You can also set a value. The following sets the `todo.priority` property to `1` when the button is clicked: | ||
### Call a function when an event happens on a ViewModel | ||
Use [can-stache-bindings.event] to listen to when an event is dispatched on | ||
a [can-component]'s [can-component.prototype.ViewModel]. | ||
In the following example, `<my-demo>` listens to `number` events from `<random-number-generator>`'s `ViewModel`: | ||
```html | ||
<button on:click="todo.priority = 1">Critical</button> | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "random-number-generator", | ||
ViewModel: { | ||
connectedCallback(){ | ||
const interval = setInterval( () => { | ||
this.dispatch({type: "number", value: Math.random()}) | ||
}, 1000); | ||
return ()=> { | ||
clearInterval(interval); | ||
}; | ||
} | ||
} | ||
}) | ||
Component.extend({ | ||
tag: "my-demo", | ||
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> | ||
`, | ||
ViewModel: { | ||
numbers: { default: ()=>[] }, | ||
addNumber(number){ | ||
this.numbers.push(number); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
Note that when properties are set on a `ViewModel` these produce events too. In the following example, `<my-demo>` listens to | ||
`number` produced when `<random-number-generator>`'s `ViewModel`'s `number` property [can-define.types.value changes]: | ||
#### [can-stache-bindings.toChild one-way to child] | ||
Updates `childProp` in `<my-component>`’s [can-component::ViewModel ViewModel] with `value` in the [can-view-scope scope]: | ||
```html | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "random-number-generator", | ||
ViewModel: { | ||
number: { | ||
value({resolve}){ | ||
const interval = setInterval( () => { | ||
resolve(Math.random()) | ||
}, 1000); | ||
return ()=> { | ||
clearInterval(interval); | ||
}; | ||
} | ||
} | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "my-demo", | ||
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> | ||
`, | ||
ViewModel: { | ||
numbers: { default: ()=>[] }, | ||
addNumber(number){ | ||
this.numbers.push(number); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
### 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](http://animejs.com/) to animate the `<div>`: | ||
```html | ||
<my-component childProp:from="value"/> | ||
<my-demo></my-demo> | ||
<script src="//cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/anime.min.js"></script> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
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 }} | ||
`, | ||
ViewModel: { | ||
todos: { | ||
default: ()=> [ | ||
{name: "animate", complete: false}, | ||
{name: "celebrate", complete: true} | ||
] | ||
}, | ||
shake(element){ | ||
anime({ | ||
targets: element, | ||
translateX: [ 10,-10,0 ], | ||
easing: 'linear' | ||
}); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
@highlight 10 | ||
> This can be read as "set `childProp` _from_ `value`". | ||
If the element does not have a [can-component::ViewModel ViewModel], updates the `child-attr` attribute or property of the | ||
element with `value` in the [can-view-scope scope]: | ||
### 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 | ||
<div child-attr:from="value"/> | ||
<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 {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
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> | ||
`, | ||
ViewModel: { | ||
enabled: {default: false}, | ||
boom() { | ||
alert("Red Alert!"); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 23 | ||
@codepen | ||
> __Note:__ If the value being passed to the component is an object, changes to the object's properties will still be visible to the component. Objects are passed by reference. See [can-stache-bindings#OneWayBindingWithObjects One Way Binding With Objects]. | ||
#### [can-stache-bindings.toParent one-way to parent] | ||
Updates `value` in the [can-view-scope scope] with `childProp` | ||
in `<my-component>`’s [can-component::ViewModel ViewModel]: | ||
### Update a component ViewModel's value from the scope | ||
Use [can-stache-bindings.toChild] to: | ||
- initialize a [can-component Component]'s [can-component.prototype.ViewModel] property value from [can-stache stache's] [can-view-scope scope], and | ||
- update the ViewModel property with the scope value changes. | ||
The following | ||
```html | ||
<my-component childProp:to="value"/> | ||
<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 {Component} from "can"; | ||
Component.extend({ | ||
tag: "percentage-slider", | ||
view: ` | ||
<div class="percent" style="width: {{this.percent}}%"></div> | ||
`, | ||
ViewModel: { | ||
percent: "number" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "my-demo", | ||
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> | ||
`, | ||
ViewModel: { | ||
value: {default: 50, type: "number"}, | ||
increase(amount){ | ||
var newValue = this.value + amount; | ||
if(newValue >= 0 && newValue <= 100) { | ||
this.value += amount; | ||
} | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 27 | ||
@codepen | ||
> This can be read as "send `childProp` _to_ `value`". | ||
[can-stache-bindings.toChild] can be used to pass the results of functions like `percent:from="this.method()"`. | ||
If the element does not have a [can-component::ViewModel ViewModel], it updates `value` | ||
in the [can-view-scope scope] with the `child-attr` attribute or property of the element. | ||
### 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 ViewModel when the `<input>`'s _change_ event fires: | ||
```html | ||
<div child-attr:to="value"/> | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<p>Name: {{this.name}}</p> | ||
<p>Update name when "change" fires: <input value:to="this.name"/></p> | ||
`, | ||
ViewModel: { | ||
name: "string" | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 9 | ||
@codepen | ||
> __Note:__ If the value being passed to the component is an object, changes to the object's properties will still be visible to the component. Objects are passed by reference. See [can-stache-bindings#OneWayBindingWithObjects One Way Binding With Objects]. | ||
#### [can-stache-bindings.twoWay two-way] | ||
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 the empty string: | ||
Updates `childProp` in `<my-component>`’s [can-component::ViewModel ViewModel] with `value` in the [can-view-scope scope] and vice versa: | ||
```html | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<p>Name: {{this.name}}</p> | ||
<p>Update name when "change" fires: <input value:to="this.name"/></p> | ||
`, | ||
ViewModel: { | ||
name: {default: "Justin"} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 12 | ||
@codepen | ||
Use `on:event:elementPropery:to` to customize which event to listen to. The following | ||
switches to the `input` event: | ||
```html | ||
<my-component childProp:bind="value"/> | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<p>Name: {{this.name}}</p> | ||
<p>Update name as you type: <input on:input:value:to="this.name"/></p> | ||
`, | ||
ViewModel: { | ||
name: {default: "Justin"} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 9 | ||
@codepen | ||
Updates the `child-attr` attribute or property of the element with `value` | ||
in the [can-view-scope scope] and vice versa: | ||
> 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 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 | ||
<div child-attr:bind="value"/> | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "random-number-generator", | ||
ViewModel: { | ||
number: { | ||
value({resolve}){ | ||
const interval = setInterval( () => { | ||
resolve(Math.random()); | ||
}, 1000); | ||
return ()=> { | ||
clearInterval(interval); | ||
}; | ||
} | ||
} | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<random-number-generator number:to="this.randomNumber"/> | ||
<h1>Your random number is {{this.randomNumber}}</h1> | ||
`, | ||
ViewModel: { | ||
randomNumber: "number" | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 24 | ||
@codepen | ||
## One Way Binding With Objects | ||
> NOTE: Just like passing an element value to the scope, passing a ViewModel value | ||
> will overwrite existing scope values. You can use `on:event:key:to="scopeValue"` | ||
> to specify the event to listen to. | ||
`childProp:from="key"` ([can-stache-bindings.toChild one-way to child]) or `child-prop:to="key"` ([can-stache-bindings.toParent one-way to parent]) is used to pass values from the current scope to a component, or from a component to the current scope, respectively. | ||
Generally, this binding only observes changes in one direction, but when [can-stache.key] is an object (POJO, DefineMap, etc), it is passed as a reference, behaving in much the same way as the following snippet. | ||
```js | ||
function component( bar ) { | ||
### Keep a parent and child in sync | ||
// changes to bar's properties are preserved | ||
bar.quux = "barfoo"; | ||
Use [can-stache-bindings.twoWay] to keep a parent and child value in sync. Use [can-stache-bindings.twoWay] | ||
to keep either an element or ViewModel value in sync with a scope value. | ||
// but replacing bar is not | ||
bar = { | ||
quux: "hello world" | ||
}; | ||
} | ||
The following keeps an `<input>`'s `.value` in sync with `this.name` in the scope: | ||
const foo = { | ||
quux: "foobar" | ||
}; | ||
component( foo ); | ||
```html | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<p>Name is currently: {{this.name}}</p> | ||
<p><input value:bind="this.name"/></p> | ||
`, | ||
ViewModel: { | ||
name: {default: ""} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 9 | ||
@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 {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<p>Name is currently: {{this.name}}</p> | ||
<p><input on:input:value:bind="this.name"/></p> | ||
`, | ||
ViewModel: { | ||
name: {default: ""} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 9 | ||
@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 a [can-component.prototype.ViewModel] in sync with a | ||
scope value: | ||
```html | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "name-editor", | ||
view: ` | ||
<input placeholder="first" value:bind="first"/> | ||
<input placeholder="last" value:bind="last"/> | ||
`, | ||
ViewModel: { | ||
first: "string", | ||
last: "string", | ||
get fullName(){ | ||
return this.first + " " + this.last; | ||
}, | ||
set fullName(newVal) { | ||
var parts = newVal.split(" "); | ||
this.first = parts[0] || ""; | ||
this.last = parts[1] || ""; | ||
} | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "my-demo", | ||
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> | ||
`, | ||
ViewModel: { | ||
name: {default: "Carol Danvers"} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@highlight 29 | ||
@codepen | ||
## Other Uses | ||
The following are some advanced or non-obvious use cases. | ||
### Pass values between siblings | ||
Sometimes you have two sibling components or elements that need to communicate and creating | ||
a value in the parent component 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 {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-drivers", | ||
view: ` | ||
<ul> | ||
{{# for(driver of this.drivers) }} | ||
<li on:click="this.selected = driver"> | ||
{{ driver.title }} {{ driver.first }} {{ driver.last }} - {{ driver.licensePlate }} | ||
</li> | ||
{{/ for }} | ||
</ul> | ||
`, | ||
ViewModel: { | ||
drivers: { | ||
default() { | ||
return [ | ||
{ title: "Dr.", first: "Cosmo", last: "Kramer", licensePlate: "543210" }, | ||
{ title: "Ms.", first: "Elaine", last: "Benes", licensePlate: "621433" } | ||
]; | ||
} | ||
}, | ||
selected: "any" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "edit-plate", | ||
view: `<input on:input='this.plateName = scope.element.value' | ||
value:from='this.plateName'/>`, | ||
ViewModel: { | ||
plateName: "string" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: 'my-demo', | ||
view: ` | ||
{{ let editing=undefined }} | ||
<my-drivers selected:to="editing"/> | ||
<edit-plate plateName:bind="editing.licensePlate"/> | ||
` | ||
}); | ||
</script> | ||
``` | ||
@highlight 41 | ||
@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 { display: block; height: 90vh; | ||
border: solid 1px black; overflow: auto; } | ||
</style> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
var isVisibleSymbol = Symbol("isVisible"); | ||
Component.extend({ | ||
tag: "in-view", | ||
view: `<content/>`, | ||
ViewModel: { | ||
connectedCallback(el) { | ||
function dispatchEvents(){ | ||
// Get all visible elmenets | ||
var visible = Array.from(el.childNodes).filter( (child) => { | ||
return child.offsetTop > el.scrollTop | ||
&& child.offsetTop <= el.scrollTop + el.clientHeight | ||
}); | ||
// dispatch event on elements that have not | ||
// been dispatched | ||
visible.forEach(function(child){ | ||
if(!child[isVisibleSymbol]) { | ||
child[isVisibleSymbol] = true; | ||
child.dispatchEvent(new Event('inview')); | ||
} | ||
}); | ||
} | ||
// Dispatch on visible elements right away | ||
dispatchEvents(); | ||
// On scroll, dispatch | ||
this.listenTo(el,"scroll", dispatchEvents); | ||
} | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<in-view> | ||
{{# for(item of this.items) }} | ||
<div on:inview="this.getData(item)"> | ||
{{item.data}} | ||
</div> | ||
{{/ for }} | ||
</in-view> | ||
`, | ||
ViewModel: { | ||
items: { | ||
default() { | ||
var items = []; | ||
for(var i = 0; i < 400; i++) { | ||
items.push({data: "unloaded"}); | ||
} | ||
return items; | ||
} | ||
}, | ||
getData(item) { | ||
item.data = "loading..." | ||
setTimeout(function(){ | ||
item.data = "loaded"; | ||
},Math.random() * 1000); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
</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 event into the global registry and uses them: | ||
```html | ||
<my-demo></my-demo> | ||
<script src="//cdnjs.cloudflare.com/ajax/libs/animejs/2.0.2/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 {Component, domEvents, enterEvent, domMutateDomEvents} from "can/everything"; | ||
domEvents.addEvent(enterEvent); | ||
domEvents.addEvent(domMutateDomEvents.inserted); | ||
Component.extend({ | ||
tag: "my-demo", | ||
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> | ||
`, | ||
ViewModel: { | ||
state: {default: "red"}, | ||
nextState(){ | ||
var states = {red: "yellow", yellow: "green", green: "red"}; | ||
this.state = states[this.state]; | ||
}, | ||
shake(element){ | ||
anime({ | ||
targets: element, | ||
translateX: [ 10,-10,0 ], | ||
easing: 'linear' | ||
}); | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
@highlight 19,24 | ||
</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 {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<input type="checkbox" checked:bind="not(this.activated)"/> Disable | ||
`, | ||
ViewModel: { | ||
activated: {default: true, type: "boolean"} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
[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. Currently, it | ||
```html | ||
<my-demo></my-demo> | ||
<style> | ||
:focus { background-color: yellow; } | ||
</style> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-demo", | ||
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> | ||
`, | ||
ViewModel: { | ||
cardNumber: "string", | ||
cvcFocus: { | ||
value({listenTo, resolve}) { | ||
listenTo("cardNumber", (ev, newVal) => { | ||
if(newVal.length === 9) { | ||
resolve(true); | ||
} else { | ||
resolve(false); | ||
} | ||
}); | ||
listenTo("cvcBlur", () => { | ||
resolve(false); | ||
}); | ||
} | ||
}, | ||
cvcNumber: "string", | ||
payFocus: { | ||
value({listenTo, resolve}) { | ||
listenTo("cvcNumber", (ev, newVal) => { | ||
if(newVal.length === 3) { | ||
resolve(true); | ||
} else { | ||
resolve(false); | ||
} | ||
}); | ||
listenTo("payBlur", () => { | ||
resolve(false); | ||
}); | ||
} | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
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 that changes to that object's `first` and `last` are visible to the `<my-demo>` component: | ||
```html | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "name-editor", | ||
view: ` | ||
<input on:input:value:bind="this.name.first"/> | ||
<input on:input:value:bind="this.name.last"/> | ||
`, | ||
ViewModel: { | ||
name: "any" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "my-demo", | ||
view: ` | ||
<p>First: {{this.name.first}}, Last: {{this.name.last}}</p> | ||
<name-editor name:from="this.name"/> | ||
`, | ||
ViewModel: { | ||
name: { | ||
default(){ | ||
return {first: "Justin", last: "Meyer"}; | ||
} | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
### 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-component>` always ensures that `parentName` is upper-cased. If you type lower-case | ||
characters in the input (example: `foo bar`), you'll see that both _Parent Name_ and _Child Name_ are left upper-cased, but not the input's value. | ||
```html | ||
<parent-component></parent-component> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "child-component", | ||
view: ` | ||
<p>Child Name: {{this.childName}}</p> | ||
<input value:bind="this.childName"/> | ||
`, | ||
ViewModel: { | ||
childName: "any" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "parent-component", | ||
view: ` | ||
<p>Parent Name: {{this.parentName}}</p> | ||
<child-component childName:bind="this.parentName"/> | ||
`, | ||
ViewModel: { | ||
parentName: { | ||
default: "JUSTIN MEYER", | ||
set(newVal){ | ||
return newVal.toUpperCase(); | ||
} | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
This happens because after `parentName` is set, [can-bind] compares `parentName`'s '`FOO BAR` to `childName`'s | ||
`foo bar`. Because the are not equal, `childName` is set to `FOO BAR`. Setting `childName` to `FOO BAR` will | ||
also try to set the `<input>` to `FOO BAR`, but because the `<input>` started the chain of changes, this change will not | ||
be allowed and a warning will be logged. See [Semaphore Warnings](#Semaphorewarnings) for more information about these warnings. | ||
This can be fixed by changing from a two-way binding to an event and [can-stache-bindings.toChild] binding as follows: | ||
```html | ||
<parent-component></parent-component> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "child-component", | ||
view: ` | ||
<p>Child Name: {{this.childName}}</p> | ||
<input value:from="this.childName" on:change="this.childName = scope.element.value"/> | ||
`, | ||
ViewModel: { | ||
childName: "any" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "parent-component", | ||
view: ` | ||
<p>Parent Name: {{this.parentName}}</p> | ||
<child-component childName:bind="this.parentName"/> | ||
`, | ||
ViewModel: { | ||
parentName: { | ||
default: "JUSTIN MEYER", | ||
set(newVal){ | ||
return newVal.toUpperCase(); | ||
} | ||
} | ||
} | ||
}); | ||
</script> | ||
``` | ||
@codepen | ||
## Warnings | ||
### Semaphore warnings | ||
You might see semaphore warnings in your application like: | ||
> ``` | ||
> can-bind: attempting to update child AttributeObservable<input.value> to new value: undefined | ||
> …but the child semaphore is at 1 and the parent semaphore is at 1. The number of allowed updates is 1. | ||
> The child value will remain unchanged; it’s currently: "". | ||
> Read https://canjs.com/doc/can-bind.html#Warnings for more information. Printing mutation history: | ||
> child AttributeObservable<input.value> set. | ||
> child AttributeObservable<input.value> NOT set. | ||
> ``` | ||
You can see this warning in the following demo: | ||
```html | ||
<my-demo></my-demo> | ||
<script type="module"> | ||
import {Component} from "can"; | ||
Component.extend({ | ||
tag: "my-drivers", | ||
view: ` | ||
<ul> | ||
{{# for(driver of this.drivers) }} | ||
<li on:click="this.selected = driver"> | ||
{{ driver.title }} {{ driver.first }} {{ driver.last }} - {{ driver.licensePlate }} | ||
</li> | ||
{{/ for }} | ||
</ul> | ||
`, | ||
ViewModel: { | ||
drivers: { | ||
default: function () { | ||
return [ | ||
{ title: "Dr.", first: "Cosmo", last: "Kramer", licensePlate: "543210" }, | ||
{ title: "Ms.", first: "Elaine", last: "Benes", licensePlate: "621433" } | ||
]; | ||
} | ||
}, | ||
selected: "any" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: "edit-plate", | ||
view: `<input on:input:value:bind='this.plateName'/>`, | ||
ViewModel: { | ||
plateName: "string" | ||
} | ||
}); | ||
Component.extend({ | ||
tag: 'my-demo', | ||
view: ` | ||
{{ let editing=undefined }} | ||
<my-drivers selected:to="editing"/> | ||
<edit-plate plateName:bind="editing.licensePlate"/> | ||
` | ||
}); | ||
</script> | ||
``` | ||
@highlight 42 | ||
@codepen | ||
The reason this is shown is because: | ||
1. `<input on:input:value:bind='this.plateName'/>` updates `plateName` to `""`. | ||
2. That attempts to set `editing.licensePlate` to `""`. But since `editing` is `undefined`, | ||
it's impossible to set a `licensePlate` property on `undefined`. Then, because bindings are | ||
sticky, we will try to pass down `undefined` as the `plateName`. However, this will not be allowed | ||
as both a child and parent updated happened in the same cycle. | ||
## 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-component] can create it's ViewModel with the values in the scope. This avoids unnecessary changes | ||
and improves perofrmance. | ||
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. |
@@ -123,83 +123,4 @@ @function can-stache-bindings.event on:event | ||
## DOM events | ||
## Use | ||
`on:el:` will listen for events on the DOM, `on:` can also be used to listen for DOM events if the element does not have a [can-component::ViewModel ViewModel]. | ||
```html | ||
<div on:click="doSomething()"/> | ||
``` | ||
By adding `on:EVENT='methodKey()'` to an element, the function pointed to | ||
by `methodKey` is bound to the element’s `EVENT` event. The function can be | ||
passed any number of arguments from the surrounding scope, or `name=value` | ||
attributes for named arguments. Direct arguments will be provided to the | ||
handler in the order they were given. | ||
The following uses `on:click='../items.splice(scope.index,1)'` to remove an | ||
item from `items` when that item is clicked on. | ||
@demo demos/can-stache-bindings/event-args.html | ||
@codepen | ||
### Special Event Types | ||
[can-stache-bindings] supports creating special event types | ||
(events that aren’t natively triggered by the DOM), which are | ||
bound by adding attributes like `on:SPECIAL='KEY'`. This is | ||
similar to [$.special](http://benalman.com/news/2010/03/jquery-special-events/) in jQuery. | ||
### on:enter | ||
`on:enter` is a special event that calls its handler whenever the enter | ||
key is pressed while focused on the current element. For example: | ||
```html | ||
<input type='text' on:enter='save()' /> | ||
``` | ||
The above template snippet would call the save method | ||
(in the [can-view-scope scope]) whenever | ||
the user hits the enter key on this input. | ||
## viewModel events | ||
To listen on a [can-component Component’s] [can-component.prototype.ViewModel ViewModel], prepend the event with `on:` (`on:vm:` can also be used to be make this more explicit) like: | ||
```html | ||
<player-edit | ||
on:close="removeEdit()" | ||
player:from="editingPlayer"/> | ||
``` | ||
ViewModels can publish events on themselves. The following `<player-edit>` component | ||
dispatches a `"close"` event on itself when its `close` method is called: | ||
```js | ||
Component.extend( { | ||
tag: "player-edit", | ||
view: stache( $( "#player-edit-stache" ).html() ), | ||
ViewModel: DefineMap.extend( { | ||
player: Player, | ||
close: function() { | ||
this.dispatch( "close" ); | ||
} | ||
} ) | ||
} ); | ||
``` | ||
The following demo uses this ability to create a close button that | ||
hides the player editor: | ||
@demo demos/can-component/paginate_next_event.html | ||
@codepen | ||
## Changing a property when an event occurs | ||
An event on either the element or viewModel can be set to bind the element’s value to a property | ||
on the scope like: | ||
```html | ||
<input type="text" value="" on:blur:value:to="myScopeProp"> | ||
``` | ||
This will set the value of myScopeProp to the input’s value anytime the input loses focus. | ||
The [can-stache-bindings] page has many examples of [can-stache-bindings.event]. |
@@ -1,2 +0,2 @@ | ||
@function can-stache-bindings.raw toChild:raw | ||
@function can-stache-bindings.raw key:raw | ||
@parent can-stache-bindings.syntaxes | ||
@@ -3,0 +3,0 @@ |
@@ -1,2 +0,2 @@ | ||
@function can-stache-bindings.toChild toChild:from | ||
@function can-stache-bindings.toChild key:from | ||
@parent can-stache-bindings.syntaxes | ||
@@ -55,11 +55,5 @@ | ||
`childProp:from="key"` is used to pass values from the scope to a component. | ||
You can use [can-stache.expressions#Callexpressions call expressions] like: | ||
The [can-stache-bindings] page has many examples of [can-stache-bindings.toChild]. Specifically: | ||
```html | ||
<player-scores scores:from="game.scoresForPlayer('Alison')"/> | ||
<player-scores scores:from="game.scoresForPlayer('Jeff')"/> | ||
``` | ||
@demo demos/can-stache-bindings/to-child.html | ||
@codepen | ||
- [can-stache-bindings#Updateanelement_svaluefromthescope Update a component ViewModel's value from the scope] | ||
- [can-stache-bindings#UpdateacomponentViewModel_svaluefromthescope Pass a value from an element to the scope] |
@@ -1,2 +0,2 @@ | ||
@function can-stache-bindings.toParent toParent:to | ||
@function can-stache-bindings.toParent key:to | ||
@parent can-stache-bindings.syntaxes | ||
@@ -78,51 +78,5 @@ | ||
Depending on whether the element has a [can-component.prototype.ViewModel ViewModel], `:to` bindings change | ||
between exporting __ViewModel properties__ or __DOM properties__. | ||
The [can-stache-bindings] page has many examples of [can-stache-bindings.toParent]. Specifically: | ||
## Exporting ViewModel properties | ||
`childProp:to="key"` can be used to export single values or the complete view model from a | ||
child component into the parent scope. Typically, the values are exported to the references scope. | ||
The following example connects the __selected__ driver in `<drivers-list>` with an editable __plateName__ in | ||
`<edit-plate>`: | ||
```html | ||
<drivers-list selected:to="scope.vars.editing"/> | ||
<edit-plate plateName:bind="scope.vars.editing.licensePlate"/> | ||
``` | ||
Click on one of the list items below and watch as its text appears in the input box. You can then edit the text and it will update in the list. | ||
@demo demos/can-stache-bindings/to-parent.html | ||
@codepen | ||
## Exporting DOM properties | ||
`child-prop:to="key"` can be used to export an attribute value into the scope. For example: | ||
```html | ||
<input value:to="name"/> | ||
``` | ||
Updates `name` in the scope when the `<input>` element’s `value` changes. | ||
## Exporting Functions | ||
You can export a function to the [can-stache/keys/scope/scope.vars parent references scope] | ||
with a binding like: | ||
```html | ||
<my-tabs addPanel:to="scope.vars.addPanel"> | ||
``` | ||
And pass the method like: | ||
```html | ||
<my-panel addPanel:from="scope.vars.addPanel" title:from="'CanJS'">CanJS Content</my-panel> | ||
``` | ||
Check it out in this demo by clicking on the list items: | ||
@demo demos/can-stache-bindings/to-parent-function.html | ||
@codepen | ||
- [can-stache-bindings#Passavaluefromanelementtothescope Pass a value from an element to the scope] | ||
- [can-stache-bindings#Passavaluefromacomponenttothescope Pass a value from a component to the scope] |
@@ -1,2 +0,2 @@ | ||
@function can-stache-bindings.twoWay twoWay:bind | ||
@function can-stache-bindings.twoWay key:bind | ||
@parent can-stache-bindings.syntaxes | ||
@@ -76,21 +76,2 @@ | ||
## Use | ||
`childProp:bind="key"` is used to two-way bind a value in a [can-component.prototype.ViewModel ViewModel] to | ||
a value in the [can-view-scope scope]. If one value changes, the other value is updated. | ||
The following two-way binds the `<edit-plate>` element’s `plateName` to the `editing.licensePlate` | ||
value in the scope. This allows `plateName` to update if `editing.licensePlate` changes and | ||
`editing.licensePlate` to update if `plateName` changes. | ||
Click on one of the list items below and watch as its text appears in the input box. You can then edit the text and it will update in the list. | ||
@demo demos/can-stache-bindings/two-way.html | ||
@codepen | ||
This demo can be expressed a bit easier with the references scope: | ||
@demo demos/can-stache-bindings/reference.html | ||
@codepen | ||
## Initialization | ||
@@ -104,1 +85,5 @@ | ||
- If both the viewModel and scope are `not undefined`, viewModel will be set to the scope value. | ||
## Use | ||
See [can-stache-bindings#Keepaparentandchildinsync Keep a parent and child in sync]. |
{ | ||
"name": "can-stache-bindings", | ||
"version": "4.7.2", | ||
"version": "4.7.3", | ||
"description": "Default binding syntaxes for can-stache", | ||
@@ -35,3 +35,3 @@ "homepage": "https://canjs.com/doc/can-stache-bindings.html", | ||
"can-attribute-encoder": "^1.1.1", | ||
"can-attribute-observable": "^1.2.0", | ||
"can-attribute-observable": "^1.2.1", | ||
"can-bind": "^1.3.0", | ||
@@ -38,0 +38,0 @@ "can-dom-data-state": "^1.0.0", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
201282
30