-
Notifications
You must be signed in to change notification settings - Fork 422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve routing to components (and testing) #4030
Comments
Looks very promising. 👍 |
Can you create another issue about having element on the viewModel? |
This has been released! There were a few packages involved in making this happen, so let’s go through them: First is can-value 1.0. import DefineMap from "can-define/map/map";
import value from "can-value";
const outer = new DefineMap({
inner: {
key: "hello"
}
});
const keyObservable = value.bind(outer, "inner.key"); Now if we read keyObservable.value; // is "hello" We can also set keyObservable.value = "aloha";
// Now outer.inner.key === "aloha" |
Next, let’s talk about can-component 4.2, which makes it possible to instantiate component instances with
The following defines a const HelloWorld = Component.extend({
tag: "hello-world",
view: `
<can-slot name="greetingTemplate" />
<content>world</content>
<ul>{{#each(items)}} {{this}} {{/each}}</ul>
`,
ViewModel: {
items: {}
}
});
// Create a new instance of our component
const componentInstance = new HelloWorld({
// values with which to initialize the component’s view model
viewModel: {
items: ["eat"]
},
// can-stache template to replace any <content> elements in the component’s view
content: "<em>{{message}}</em>",
// <can-template> strings rendered by can-stache with the scope
templates: {
greetingTemplate: "{{greeting}}"
},
// scope with which to render the <content> and templates
scope: {
greeting: "Hello",
message: "friend"
}
});
myGreetingInstance.element; // is like <my-greeting>Hello <em>friend</em> <ul> <li>eat</li> </ul></my-greeting>
myGreetingInstance.viewModel; // is HelloWorld.ViewModel{items: ["eat"]} Changing the component’s view model will cause its element and any bindings to be updated: myGreetingInstance.viewModel.items.push("sleep");
myGreetingInstance.element; // is like <my-greeting>Hello <em>friend</em> <ul> <li>eat</li> <li>sleep</li> </ul></my-greeting> |
Now let’s tie those new features of The import Component from "can-component";
import DefineMap from "can-define/map/map";
import value from "can-value";
const appVM = new DefineMap({
association: "friend"
});
const MyGreeting = Component.extend({
tag: "my-greeting",
view: "{{greeting}} {{subject}}",
ViewModel: {
greeting: "string",
subject: "string"
}
});
const myGreetingInstance = new MyGreeting({
viewModel: {
greeting: "Hello",
subject: value.bind(appVM, "association")
}
});
myGreetingInstance.element; // is <my-greeting>Hello friend</my-greeting>
myGreetingInstance.viewModel; // is MyGreeting.ViewModel{subject: "friend"} The way the component is instantiated above is similar to this example below, assuming it’s rendered by <my-greeting greeting:raw="Hello" subject:bind="association"></my-greeting>
const appVM = new DefineMap({
family: {
first: "Milo",
last: "Flanders"
}
});
const NameComponent = Component.extend({
tag: "name-component",
view: "{{fullName}}",
ViewModel: {
givenName: "string",
familyName: "string",
get fullName() {
return this.givenName + " " + this.familyName;
}
}
});
const componentInstance = new NameComponent({
viewModel: {
givenName: value.from(appVM, "family.first"),
familyName: value.bind(appVM, "family.last"),
fullName: value.to(appVM, "family.full"),
}
}); The way the component is instantiated above is similar to this example below, assuming it’s rendered by <my-greeting
givenName:from="family.first"
familyName:bind="family.last"
fullName:to="family.full"
></my-greeting> This will result in an {
family: {
first: "Milo",
full: "Milo Flanders",
last: "Flanders"
}
} Changing the component’s view model will cause its element and any bindings to be updated: componentInstance.viewModel.familyName = "Smith";
componentInstance.element; // is <name-component>Milo Smith</name-component>
appVM.family.last; // is "Smith" If you voted for this proposal and the above doesn’t excite you, let me know how I can explain this better because it should BLOW YOUR MIND. |
For the icing on this cake, I’d like to show you what’s new in can-stache 4.9. This release makes it possible to render component instances in a template with the “unescaped” (triple-curly) tags: import Component from "can-component";
import stache from "can-stache";
const MyGreeting = Component.extend({
tag: "my-greeting",
view: "<p>Hello {{subject}}</p>",
ViewModel: {
subject: "string"
}
});
const myGreetingInstance = new MyGreeting({
viewModel: {
subject: "friend"
}
});
const template = stache("<div>{{{componentInstance}}}</div>");
const fragment = template({
componentInstance: myGreetingInstance
});
fragment; //-> <div><my-greeting><p>Hello friend</p></my-greeting></div>
|
Creating new component instances in your view model and rendering them with https://gist.github.com/chasenlehara/3e20479e3cf0fa53510e00bd7062c535#file-route-example-js-L34 Hurrah! 🎉 Of course, there are more details to be shown in the upcoming routing and testing guides, but hopefully this gives you enough info to start using these new features in your application today. 🏁 |
All of this has been released in can@4.3.0. |
TLDR: It's difficult to set up routing between the state set on the application view model and the components and bindings that need to be rendered. This proposal provides a mechanism to make this easier by:
new
.bindings
.Motivation
Currently, most of our guides show using
can-stache
to figure out how to render a component. This creates cumbersome switch statements in stache like this:Logic is MUCH harder to write in stache than in JS.
So, the alternative way Bitballs renders its page components is a popular pattern is to create a
{{{pageComponent(scope)}}}
in yourindex.stache
and the helper will progressively load the required component and render it in the template.We’d like to make this pattern easier so any component can be instantiated by its constructor function and rendered in a template, which would make the above code more simple and ease additional use cases (such as testing components).
This pattern is easy to get wrong as
nodeLists
matter. Something like the following might leak because nodeLists might not be setup correctly:Overview
There are a few parts to this:
Creating components
Checkout this Routing Example.
Proposal:
Where:
ComponentType
is an extendedComponent
componentInstance
is an instance of that component (NOT an element). To access the element and view model:componentInstance.element
componentInstance.viewModel
viewModel
- An object of strings to values to create theViewModel
with. For example:viewModel
instance'sname
to "Justin". If values are observable values (like computes, Observations, SimpleObservable, etc), this will be used to setup bindings similar tocan-view-stache
bindings. For example, the following will two-way bind between theviewModel
's name and thesimpleObservable
:getValue
->name:from='observable'
setValue
->name:to='observable'
name:bind='observable'
templates
- An optional object of slot names to renderers:content
- Just like templates, but for the<content>
tag.Creating observables for to, from, and two-way binding.
I think
DefineMap
andobserve.Object
can get.from
,.to
and.bind
methods:Alternatively, we don't need to shorten them that much:
I think
can-value
should have these built in so they can be used on anything:How these get rendered.
If
new Component()
returns an instance of a component, we need to get the element to add to the DOM. We might also need the ability to talk tonodeLists
.So, I think we need a way for entities like components (maybe Controls too), to tell
stache
how to hook them up. This is probably a symbol. I proposecan.element
for now.TESTING
This really helps testing. No more having to call out to stache to test a component:
How we teach this
In addition to showing this pattern as one of the API signatures in can-component’s docs, we would most likely show this pattern in our new routing guide and a future testing guide.
The text was updated successfully, but these errors were encountered: