Core
The best, most hardened and generally useful libraries in CanJS.
Use
CanJS’s core libraries are the best, most hardened and generally useful modules. Each module is part of an independent package, so you should install the ones you use directly:
npm install can-value can-stache-element can-realtime-rest-model can-observable-object can-observable-array can-route can-route-pushstate --save
Let’s explore each module a bit more.
can-value
can-values represent an observable value. A value can contain its own value and notify listeners of changes like:
import { value } from "can";
let name = value.with("Justin");
// read the value
console.log(name.value); //-> "Justin"
name.on((newVal, oldVal) => {
console.log(newVal); //-> "Matthew"
console.log(oldVal); //-> "Justin"
});
name.value = "Matthew";
More commonly, a value derives its value from other observables. The following
info
compute derives its value from a person
object, hobbies
array, and age
value:
import { ObservableObject, ObservableArray, value } from "can";
let person = new ObservableObject({ first: "Justin", last: "Meyer" }),
hobbies = new ObservableArray(["js", "bball"]),
age = value.with(33);
let info = value.returnedBy(function(){
return person.first +" "+ person.last + " is " + age.value +
"and likes " + hobbies.join(", ") + ".";
});
console.log(info.value); //-> "Justin Meyer is 33 and likes js, bball."
info.on((newVal) => {
console.log(newVal); //-> "Justin Meyer is 33 and likes js."
});
hobbies.pop();
can-observable-object and can-observable-array
can-observable-object and can-observable-array allow you to create observable
objects and arrays with well-defined properties. You can
define a property’s type initial value, enumerability, getter-setters and much more.
For example, you can define the behavior of a Todo
type and a TodoArray
type as follows:
import { ObservableObject, ObservableArray, type } from "can";
class Todo extends ObservableObject {
static props = {
// A todo has a:
// .name that’s a string
name: String,
complete: { // .complete that’s
type: Boolean, // a boolean
default: false // initialized to false
},
// .dueDate that’s a date
dueDate: Date,
get isPastDue(){ // .pastDue that returns if the
return new Date() > this.dueDate; // dueDate is before now
}
};
toggleComplete() { // .toggleComplete method that
this.complete = !this.complete; // changes .complete
}
}
class TodoArray extends ObservableArray {
static props = {
get completeCount(){ // has .completeCount
return this.filter( // that returns
(todo) => todo.complete // # of
) // complete todos
.length;
}
};
static items = type.convert(Todo); // has numeric properties
// as todos
}
This allows you to create a Todo, read its properties, and call its methods like:
import { ObservableObject } from "can";
class Todo extends ObservableObject {
static props = {
// A todo has a:
// .name that’s a string
name: String,
complete: { // .complete that’s
type: Boolean, // a boolean
default: false // initialized to false
},
// .dueDate that’s a date
dueDate: Date,
get isPastDue(){ // .pastDue that returns if the
return new Date() > this.dueDate; // dueDate is before now
}
};
toggleComplete() { // .toggleComplete method that
this.complete = !this.complete; // changes .complete
}
}
const dishes = new Todo({
name: "do dishes",
// due yesterday
dueDate: new Date(new Date() - 1000 * 60 * 60 * 24)
});
console.log(dishes.name); //-> "do dishes"
console.log(dishes.isPastDue); //-> true
console.log(dishes.complete); //-> false
dishes.toggleComplete();
console.log(dishes.complete); //-> true
And it allows you to create a TodoArray
, access its items and properties
like:
import { ObservableObject, ObservableArray, type } from "can";
class Todo extends ObservableObject {
static props = {
// A todo has a:
// .name that’s a string
name: String,
complete: { // .complete that’s
type: Boolean, // a boolean
default: false // initialized to false
},
// .dueDate that’s a date
dueDate: Date,
get isPastDue(){ // .pastDue that returns if the
return new Date() > this.dueDate; // dueDate is before now
}
};
toggleComplete() { // .toggleComplete method that
this.complete = !this.complete; // changes .complete
}
}
class TodoArray extends ObservableArray {
static props = {
get completeCount(){ // has .completeCount
return this.filter( // that returns
(todo) => todo.complete // # of
) // complete todos
.length;
}
};
static items = type.convert(Todo); // has numeric properties
// as todos
}
const dishes = new Todo({
name: "do dishes",
// due yesterday
dueDate: new Date(new Date() - 1000 * 60 * 60 * 24)
});
dishes.toggleComplete();
const todos = new TodoArray([dishes, { name: "mow lawn", dueDate: new Date() }]);
console.log(todos.length); //-> 2
console.log(todos[0].complete); //-> true
console.log(todos.completeCount); //-> 1
These observables provide the foundation for data connection (models), component properties and even routing in your application.
can-set
[can-set] models a service layer’s behavior as a [can-set.Algebra set.Algebra]. Once modeled, other libraries such as can-connect or can-fixture can add a host of functionality like: real-time behavior, performance optimizations, and simulated service layers.
A todosAlgebra
set algebra for a GET /api/todos
service might look like:
import set from "can-set";
let todosAlgebra = new set.Algebra(
// specify the unique identifier property on data
set.prop.id("_id"),
// specify that completed can be true, false or undefined
set.prop.boolean("complete"),
// specify the property that controls sorting
set.prop.sort("orderBy")
)
This assumes that the service:
- Returns data where the unique property name is
_id
:GET /api/todos -> [{_id: 1, name: "mow lawn", complete: true}, {_id: 2, name: "do dishes", complete: false}, ...]
- Can filter by a
complete
property:GET /api/todos?complete=false -> [{_id: 2, name: "do dishes", complete: false}, ...]
- Sorts by an
orderBy
property:GET /api/todos?orderBy=name -> [{_id: 2, name: "do dishes", complete: false}, {_id: 1, name: "mow lawn", complete: true}]
In the next section will use todoAlgebra
to build a model with can-connect.
can-connect
can-connect connects a data type, typically an ObservableObject
and associated ObservableArray
,
to a service layer. This is often done via the
can-rest-model module which bundles many common behaviors
into a single api:
import { ObservableObject, ObservableArray, restModel } from "can";
class Todo extends ObservableObject {
static props = {
// ...
};
}
class TodoArray extends ObservableObject {
static props = {
// ...
};
static items = Todo;
}
const connection = restModel({
url: "/api/todos",
ObjectType: Todo,
ArrayType: TodoArray
});
baseMap
extends the Object type, in this case, Todo
, with
the ability to make requests to the service layer.
- Get a list of Todos
Todo.getList({ complete: true }).then((todos) => {});
- Get a single Todo
Todo.get({ _id: 6 }).then((todo) => {});
- Create a Todo
const todo = new Todo({ name: "do dishes", complete: false }); todo.save().then((todo) => {});
- Update an already created Todo
todo.complete = true; todo.save().then((todo) => {});
- Delete a Todo
todo.destroy().then((todo) => {});
can-stache
can-stache provides live binding mustache and handlebars syntax. While
templates should typically be loaded with a module loader like steal-stache,
you can create a template programmatically that lists out todos within a
promise loaded from Todo.getList
like:
import { stache, ObservableObject, ObservableArray, restModel } from "can";
class Todo extends ObservableObject {
static props = {
// ...
};
}
class TodoArray extends ObservableObject {
static props = {
// ...
};
static items = Todo;
}
const connection = restModel({
url: "/api/todos",
ObjectType: Todo,
ArrayType: TodoArray
});
// Creates a template
let template = stache(`
<ul>
{{# if(this.todos.isPending) }}<li>Loading…</li>{{/ if }}
{{# if(this.todos.isResolved) }}
{{# for(todo of this.todos.value) }}
<li class="{{# todo.complete }}complete{{/ todo.complete }}">{{ todo.name }}</li>
{{else}}
<li>No todos</li>
{{/for}}
{{/if}}
</ul>
`);
// Calls the template with some data
let fragment = template({
todos: Todo.getList({})
});
// Inserts the result into the page
document.body.appendChild(fragment);
can-stache templates use magic tags like {{}}
to control what
content is rendered. The most common forms of those magic tags are:
- {{key}} - Insert the value at
key
in the page. Ifkey
is a function or helper, run it and insert the result. - {{#key}}...{{/key}} - Render the content between magic tags based on some criteria.
can-stache templates return document fragments that update whenever their source data changes.
can-stache-element
can-stache-element creates custom elements with unit-testable properties. It combines a view model created by can-observable-object with a template created by can-stache.
<todos-list></todos-list>
<script type="module">
import { StacheElement, stache, ObservableObject, ObservableArray, restModel } from "can";
class Todo extends ObservableObject {
static props = {
// ...
};
}
class TodoArray extends ObservableObject {
static props = {
// ...
};
static items = Todo;
}
const connection = restModel({
url: "/api/todos",
ObjectType: Todo,
ArrayType: TodoArray
});
class TodosList extends StacheElement {
static view = `
<ul>
{{# if(this.todos.isPending) }}<li>Loading…</li>{{/ if }}
{{# if(this.todos.isResolved) }}
{{# for(todo of this.todos.value) }}
<li class="{{# todo.complete }}complete{{/ todo.complete }}">{{ todo.name }}</li>
{{else}}
<li>No todos</li>
{{/for}}
{{/if}}
</ul>
`;
// Defines the todos-list component’s properties
static props = {
// An initial value that is a promise containing the
// list of all todos.
todos: {
get default() {
return Todo.getList({});
}
},
// A method that toggles a todo’s complete property
// and updates the todo on the server.
toggleComplete(todo) {
todo.complete = !todo.complete;
todo.save();
}
};
}
customElements.define("todos-list", TodosList);
</script>
can-stache-bindings
can-stache-bindings provides custom attributes for can-stache event and data bindings.
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.
Event binding examples:
<!-- calls `toggleComplete` when the li is clicked -->
<li on:click="toggleComplete(.)"/>
<!-- calls `resetData` when cancel is dispatched on `my-modal`’s view model -->
<my-modal on:cancel="resetData()"/>
One-way to child examples:
<!-- updates input’s `checked` property with the value of complete -->
<input type="checkbox" checked:from="complete"/>
<!-- updates `todo-lists`’s `todos` property with the result of `getTodos`-->
<todos-list todos:from="getTodos(complete=true)"/>
One-way to parent examples:
<!-- updates `complete` with input’s `checked` property -->
<input type="checkbox" checked:to="complete"/>
<!-- updates `todosList` with `todo-lists`’s `todos` property -->
<todos-list todos:to="todosList"/>
Two-way examples:
<!-- Updates the input’s `value` with `name` and vice versa -->
<input type="text" value:bind="name"/>
<!-- Updates `date-picker`’s `date` with `dueDate` and vice versa -->
<date-picker date:bind="dueDate"/>
can-route and can-route-pushstate
can-route connects an ObservableObject
’s properties to values in the
url. Create an object type, connect it to the url, and begin routing like:
import { ObservableObject, route } from "can";
class AppViewModel extends ObservableObject {
static props = {
// Sets the default type to string
todoId: String,
todo: {
get: function(){
if(this.todoId) {
return Todo.get({_id: this.todoId})
}
}
}
};
static items = String;
}
const appViewModel = new AppViewModel();
route.data = appViewModel;
route.start();
When the url changes, to something like #!&todoId=5
, so will the
appViewModel
’s todoId
and todo
property:
appViewModel.todoId //-> "5"
appViewModel.todo //-> Promise<Todo>
Similarly, if appViewModel
’s todoId
is set like:
appViewModel.todoId = 6;
The hash will be updated:
window.location.hash //-> "#!&todoId=6"
The route.register function can be used to specify pretty routing rules that translate property changes to a url and a url to property changes. For example,
// a route like:
route.register("todo/{todoId}");
// and a hash like:
window.location.hash = "#!todo/7";
// produces an appViewModel like:
appViewModel.serialize() //-> {route: "todo/{todoId}", todoId: "7"}
can-route-pushstate adds pushstate support.
To use it, set route.urlData to an instance of RoutePushstate
:
import { route, RoutePushstate } from "can";
route.urlData = new RoutePushstate();
Want to learn more?
If you haven’t already, check out the Guides page on how to learn CanJS. Specifically, you’ll want to check out the Chat Guide and TodoMVC Guide to learn the basics of using CanJS’s core libraries. After that, check out the Reading the API Docs on how to use and learn from these API docs.