Open justinbmeyer opened 6 years ago
can-component new VM(props)
vs canReflect.update(props)
I think can-observe pairs better with plain objects and using can-compute directly, rather than OOP style with methods and getter/setters. Here's the TodoMVC example using observe+computes:
function todosVM(todos) {
var active = compute(() => filter(todos, {complete: false}));
var complete = compute(() => filter(todos, {complete: true}));
var allComplete = compute(() => todos.length === complete().length);
var saving = compute(() => filter(todo => isSaving(todo)));
function updateCompleteTo(value) {
todos.forEach(function(todo){
todo.complete = value;
saveTodo(todo);
});
}
function destroyComplete() {
complete().forEach(todo => destroyTodo(todo));
}
return {
active, complete, allComplete, saving,
updateCompleteTo, destroyComplete
};
}
var todos = observe([]);
var vm = todosVM(todos);
document.body.appendChild(view(vm));
@matthewp how would this work w/ composition / can-component?
Also, this doesn't show a version of the async getter logic (which requires some sort of binding and changing of a value, coupled with the ability to tear-down that binding)
Probably something like the old viewModel: function(){}
. I'm not sure if this would still work or not, but something like this:
Component.extend({
tag: "todo-app",
view,
viewModel: function(props, scope) {
var todos = scope.compute("todos");
return todosVM(todos);
}
});
Since todos
is a compute now, the todosVM implementation would change to call the function in all of the places where it needs an array.
Here's what todomvc might look like:
class Todo extends observe.Object {
constructor(props) {
super(props);
this.complete = false;
}
}
class TodoList extends observe.Array {
get active() {
return this.filter({
complete: false
});
}
get complete() {
return this.filter({
complete: true
});
}
get allComplete() {
return this.length === this.complete.length;
}
get saving() {
return this.filter(function(todo) {
return todo.isSaving();
});
}
updateCompleteTo: function(value) {
this.forEach(function(todo) {
todo.complete = value;
todo.save();
});
}
destroyComplete: function() {
this.complete.forEach(function(todo) {
todo.destroy();
});
}
}
superMap({
url: "/api/todos",
Map: Todo,
List: TodoList,
name: "todo",
algebra: todoAlgebra
});
Component.extend({
tag: "todo-create",
view: stache.from("todo-create-template"),
ViewModel: class TodoCreateVM extends observe.Object {
constructor(props) {
super(props);
this.todo = new Todo();
}
createTodo: function() {
this.todo.save().then(() => {
this.todo = new Todo();
});
}
}
});
Component.extend({
tag: "todo-list",
view: stache.from("todo-list-template"),
ViewModel: class TodoListVM extends observe.Object {
isEditing(todo) {
return todo === this.editing;
}
edit(todo) {
this.backupName = todo.name;
this.editing = todo;
}
cancelEdit() {
if (this.editing) {
this.editing.name = this.backupName;
}
this.editing = null;
}
updateName() {
this.editing.save();
this.editing = null;
}
}
});
class AppVM extends observe.Object {
connectedCallback() {
this.listensTo("todosPromise", (promise) => {
promise.then((todos) => {
this.todosList = todos;
});
});
}
get todosPromise() {
if (!this.filter) {
return Todo.getList({});
} else {
return Todo.getList({
complete: this.filter === "complete"
});
}
}
get allChecked() {
return this.todosList && this.todosList.allComplete;
}
set allChecked(newVal) {
this.todosList && this.todosList.updateCompleteTo(newVal);
}
}
route.data = new AppVM();
route("{filter}");
route.start();
can.Component.extend({
tag: "todo-mvc",
viewModel: route.data,
view: stache.from("todomvc-template")
});
can-tag
proposal: https://gist.github.com/justinbmeyer/ecf98e0ff7bcc166493877438f5c3740
Things to know:
can.Object
connectedCallback
, listenTo
, stopListening
can.Array
can.tag
- automounted by default, can-stache
and can-stache-bindings
implicitlycan.route
can-route
needs non-enumerable propertiesOther:
can-fixture
, can-set
, can.connect.baseMap
tldr; I'm having trouble figuring out how to integrate
can-observe
into CanJS to make it work well with TodoMVC.Current Working Example
Problems:
Type Approaches:
can-define
-like thing (can-observable
)Getter approaches
old-school constructor / prototype functions
class, no decorators
class and decorators
improved can-define-like thing (can-observable)
Getter approaches
The built-in
getter
The problem with the built-in getter is that:
getter
is called many times.getter
is typically on the prototype, but called withthis
as the instance. This makes implementation tricky.We'd need to somehow "install" an
Observation
"upward" in theinstance
proxy during an "observing" read. While this is achievable, I'm wary of making it the default behavior. Objects re-call their getter over and over. This is why I'm sorta favoring thememoize
approach. Though it would need some sugar for observing tests:Furthermore, to make these bind-able outside of reads, we'd need to have bindings able to create the "observation" ... likely by exploring the proxies beneath themselves. That's sorta odd (and hopefully not too expensive).
memoize
avoids this problem. A function is called. Memoize figures out the observable to bind to.Lifecycle hooks to the rescue?
For things like async getters (maybe even getters), we could encourage people to use the lifecycle hooks: https://github.com/canjs/can-define/issues/288
Todo and TodoList
Use with old-school constructor / prototype functions
Use with class, no decorators
Use with class and decorators
can-define ish thing (w/ proxy and some other improvements)