API Docs
Welcome to the CanJS API documentation! This page is a CHEAT-SHEET for the most common APIs within CanJS. Read the Technology Overview page for background on the following APIs.
Custom Element Basics
Custom elements are defined with Component. The following defines
a <my-counter>
widget and includes it in the page:
<!-- Adds the custom element to the page -->
<my-counter></my-counter>
<script type="module">
import { Component } from "can";
// Extend Component to define a custom element
Component.extend({
// The name of the custom element
tag: "my-counter",
// The HTML content within the custom element.
// - {{count}} is a `stache` magic tag.
// - `on:click` is a `stache` event binding.
// Read the VIEWS section below for more details. 👀
view: `
Count: <span>{{this.count}}</span>
<button on:click='this.increment()'>+1</button>
`,
// Defines a DefineMap used to control the
// logic of this custom element.
// Read the OBSERVABLES section below for more details. 👀
ViewModel: {
count: {default: 0},
increment() {
this.count++;
}
}
});
</script>
A component's:
- ViewModel is defined with the Observables APIs documented below.
- view is defined with the Views APIs documented below.
Also, the Element Bindings section shows how to pass data between components.
Observables
Define custom observable key-value types with DefineMap.
DefineMap
is used to organize the logic of both your Component's ViewModel and your Data Models. The logic
is expressed as properties and methods. Property behaviors are defined within a PropDefinition.
The following defines a Todo
type with numerous property behaviors and
a toggleComplete
method.
import {DefineMap} from "can";
// -------------------------------
// Define an observable Todo type:
// -------------------------------
const Todo = DefineMap.extend("Todo",{
// `id` is a Number, null, or undefined and
// uniquely identifies instances of this type.
id: { type: "number", identity: true },
// `complete` is a Boolean, null or undefined
// and defaults to `false`.
complete: { type: "boolean", default: false },
// `dueDate` is a Date, null or undefined.
dueDate: "date",
// `isDueWithin24Hours` property returns if the `.dueDate`
// is in the next 24 hrs. This is a computed property.
get isDueWithin24Hours(){
let msLeft = this.dueDate - new Date();
return msLeft >= 0 && msLeft <= 24 * 60 * 60 * 1000;
},
// `name` is a String, null or undefined.
name: "string",
// `nameChangeCount` increments when `name` changes.
nameChangeCount: {
value({listenTo, resolve}) {
let count = resolve(0);
listenTo("name", ()=> {
resolve(++count);
})
}
},
// `owner` is a custom DefineMap with a first and
// last property.
owner: {
Type: {
first: "string",
last: "string"
}
},
// `tags` is an observable list of items that
// defaults to including "new"
tags: {
default(){
return ["new"]
}
},
// `toggleComplete` is a method
toggleComplete(){
this.complete != this.complete;
}
});
// -----------------------------------------------------------
// Create and use instances of the observable key-value type:
// -----------------------------------------------------------
// Create a todo instance:
const todo = new Todo({name: "Learn Observables"});
// Change a property:
todo.dueDate = new Date().getTime() + 1000*60*60;
// Listen to changes
let handler = function(event, newValue, oldValue){
console.log(newValue) //-> "Learn DefineMap"
};
todo.listenTo("name", handler);
todo.name = "Learn DefineMap";
// Stop listening to changes
todo.stopListening("name", handler);
// Stop listening to all registered handlers
todo.stopListening();
// Call a method
todo.toggleComplete();
console.log(todo.complete) //-> true
// Assign properties
todo.assign({
owner: {
first: "Justin", last: "Meyer"
}
});
// Serialize to a plain JavaScript object
console.log( todo.serialize() ) //-> {
// complete: true,
// dueDate: Date,
// name: "Learn DefineMap",
// nameChangeCount: 1,
// owner: {first: "Justin", last: "Meyer"},
// tags: ["new"]
// }
Define observable list types with can-define/list/list:
import {DefineList} from "can";
import Todo from "//canjs.com/demos/api/todo.mjs";
// -----------------------------------
// Define an observable TodoList type:
// -----------------------------------
const TodoList = DefineList.extend("TodoList",{
// Specify the behavior of items in the TodoList
"#": {Type: Todo},
// Create a computed `complete` property
get complete(){
// Filter all complete todos
return this.filter({complete: true});
}
});
// -----------------------------------
// Create and use instances of observable list types:
// -----------------------------------
// Create a todo list
const todos = new TodoList([
{id: 1, name: "learn observable lists"},
new Todo({id: 2, name: "mow lawn", complete: true})
])
// Read the length and access items
console.log(todos.length) //-> 2
console.log(todos[0]) //-> Todo{id: 1, name: "learn observable lists"}
// Read properties
console.log(todos.complete) //-> TodoList[Todo{id: 2, name: "mow lawn", complete: true}]
// Listen for changes:
todos.listenTo("length", (event, newLength, oldLength) => {
console.log(newLength) //-> 1
})
todos.listenTo("add", function(event, addedItems, index){})
todos.listenTo("remove", function(event, removedItems, index){
console.log(removedItems.length, index) //-> 1, 1
})
// Make changes:
todos.pop();
// Call non-mutating methods
var areSomeComplete = todos.some(function(todo){
return todo.complete === true;
});
console.log( areSomeComplete ) //-> false
Ecosystem APIs
Create and use observable objects and arrays with can-observe:
import {observe} from "can";
// Create an observable object
const todo = observe( {name: "dishes"} );
// get, set and delete properties as usual
todo.name //-> "dishes"
todo.id = 1;
delete todo.id;
// Create an observable array
const todos = observe([todo]);
// use the array as usual
todos.push({
name: "lawn"
});
todos[1].name //-> "lawn"
Define observable objects types:
class Todo extends observe.Object {
constructor(props){
super(props);
// identity?
if(this.hasOwnProperty("complete")) {
this.complete = false;
}
}
// `isDueWithin24Hours` property returns if the `.dueDate`
// is in the next 24 hrs.
get isDueWithin24Hours(){
let msLeft = this.dueDate - new Date();
return msLeft >= 0 && msLeft <= 24 * 60 * 60 * 1000;
}
// `nameChangeCount` increments when `name` changes.
@observe.resolvedBy
nameChangeCount({listenTo, resolve}) {
let count = resolve(0);
listenTo("name", ()=> {
resolve(++count);
})
}
// `toggleComplete` is a method
toggleComplete(){
this.complete != this.complete;
}
}
Infrastructure APIs
const fullName = new Observation( function() {
return person.first + " " + person.last;
} );
Views
Render a template that updates the page when any data changes using can-stache:
import {stache} from "can";
import Todo from "//canjs.com/demos/api/todo.mjs";
// Create a template / view
let view = stache(`<p>I need to {{this.name}}</p>`);
const todo = new Todo({name: "learn views"});
// Render the template into document fragment
let fragment = view(todo);
// Insert fragment in the page
document.body.appendChild(fragment);
Common can-stache tags and built in helpers:
View | Data | Result |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<stache-examples></stache-examples>
<script type="module">
import { Component } from "can";
// Extend Component to define a custom element
Component.extend({
tag: "stache-examples",
view: `
<p>{{this.escapeValue}}</p>
<p>\{{{this.unescapeValue}}}</p>
<p>
{{# if( this.truthyValue ) }}
Hi
{{else}}
Bye
{{/ if }}
</p>
<p>
{{# if( this.promise.isPending ) }}
Pending
{{/ if }}
{{# if( this.promise.isRejected ) }}
Rejected {{this.promise.reason}}
{{/ if }}
{{# if( this.promise.isResolved ) }}
Resolved {{this.promise.value}}
{{/ if }}
</p>
<ul>
{{# for(todo of this.todos) }}
<li>
{{todo.name}}-{{this.owner.first}}
</li>
{{/ for }}
</ul>
<p>
{{# eq(this.eqValue,22) }}
YES
{{else}}
NO
{{/ eq }}
</p>
<p>
{{let first=this.owner.first}}
{{first}} {{this.owner.last}}
</p>
`,
ViewModel: {
escapeValue: {default: "<b>esc</b>"},
unescapeValue: {default: "<b>unescape</b>"},
truthyValue: {default: true },
promise: {
default: () => Promise.resolve("Yo")
},
todos: {
default: ()=> [ {name: "lawn"}, {name: "dishes"} ]
},
owner: {
default() {
return {
first: "Bohdi",
last: "Meyer"
};
}
},
eqValue: {default: 22}
}
});
</script>
Common can-stache expressions:
View | Data | Result |
---|---|---|
|
|
|
|
|
|
|
|
|
<stache-examples></stache-examples>
<script type="module">
import { Component } from "can";
// Extend Component to define a custom element
Component.extend({
tag: "stache-examples",
view: `
<p>{{ [key] }}</p>
<p>{{ addArgs(age, 2) }}</p>
<p>{{ addProps(v1=age v2=2) }}</p>
`,
ViewModel: {
age: {default: 3},
key: {default: "age"},
addArgs(v1, v2) {
return v1+v2;
},
addProps(vals){
return vals.v1+vals.v2;
}
}
});
</script>
Element Bindings
Listen to events on elements, read data, write data, or cross-bind data on elements with can-stache-bindings:
View | Roughly Equivalent Code |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<stache-examples></stache-examples>
<style>
.shake {
/* Start the shake animation and make the animation last for 0.5 seconds */
animation: shake 0.5s;
}
@keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
10% { transform: translate(-1px, -2px) rotate(-1deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
30% { transform: translate(3px, 2px) rotate(0deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
50% { transform: translate(-1px, 2px) rotate(-1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
70% { transform: translate(3px, 1px) rotate(-1deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
90% { transform: translate(1px, 2px) rotate(0deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
</style>
<script type="module">
import { Component } from "can";
// Extend Component to define a custom element
Component.extend({
tag: "stache-examples",
view: `
<p>Updates when the todo's name changes:
<input value:from='this.todo.name'/>
</p>
<p>Updates the todo's name when the input's <code>change</code> event fires:
<input value:to='this.todo.name'/>
</p>
<p>Updates the todo's name when the input's <code>input</code> event fires:
<input on:input:value:to='this.todo.name'/>
</p>
<p>Updates when the todo's name changes and update the
todo's name when the input's <code>change</code> event fires:
<input value:bind='this.todo.name'/>
</p>
<p>Calls the todo's <code>sayHi</code> method when the button
is clicked:
<button on:click="this.todo.sayHi()">Say Hi</button>
</p>
<p>Animate the div when the todo's <code>name</code> event fires:
<div on:name:by:todo='this.shake(scope.element)'
on:animationend='this.removeShake(scope.element)'>
{{this.todo.name}}
</div>
</p>
`,
ViewModel: {
sayHi: function(){
console.log("the ViewModel says hi");
},
shake(element) {
element.classList.add("shake");
},
removeShake(element) {
element.classList.remove("shake");
},
todo: {
default(){
return {
name: "",
sayHi(){
console.log("the todo says hi");
}
}
}
}
}
});
</script>
Custom Element Bindings
Similar to the bindings on normal elements in the previous section, you can
listen to events on custom elements, read, write or cross-bind ViewModel
data with can-stache-bindings.
The following shows examples of passing data to and from
the <my-counter>
element in the Custom Elements Basics
section:
<!-- Listens to when count changes and passes the value to doSomething -->
<my-counter on:count="doSomething(scope.viewModel.count)"></my-counter>
<!-- Starts counting at 3 -->
<my-counter count:from="3"></my-counter>
<!-- Starts counting at startCount -->
<my-counter count:from="startCount"></my-counter>
<!-- Update parentCount with the value of count -->
<my-counter count:to="parentCount"></my-counter>
<!-- Cross bind parentCount with count -->
<my-counter count:bind="parentCount"></my-counter>
<!-- Adds the custom element to the page -->
<my-app></my-app>
<script type="module">
import { Component } from "can";
Component.extend({
tag: "my-counter",
view: `
Count: <span>{{this.count}}</span>
<button on:click='this.increment()'>+1</button>`,
ViewModel: {
count: {default: 0},
increment() {
this.count++;
}
}
});
Component.extend({
tag: "my-app",
view: `
<p>Calls <code>sayHi</code> when <code>count</code> changes.
<my-counter on:count="this.sayHi(scope.viewModel.count)"></my-counter>
</p>
<p>Start counting at 3.
<my-counter count:from="3"></my-counter>
</p>
<p>Start counting at <code>startCount</code> ({{this.startCount}}).
<my-counter count:from="this.startCount"></my-counter>
</p>
<p>Update <code>parentCount</code> ({{this.parentCount}}) with the value of count.
<my-counter count:to="this.parentCount"></my-counter>
</p>
<p>Update <code>bindCount</code> ({{this.bindCount}}) with the value of count.
<my-counter count:bind="this.bindCount"></my-counter>
</p>
`,
ViewModel: {
sayHi(count){
console.log("The MyApp ViewModel says hi with",count);
},
startCount: {default: 4},
parentCount: {},
bindCount: {default: 10}
}
});
</script>
Pass can-template views to custom elements to customize layout:
<my-counter count:from="5">
<can-template name="incrementButton">
<button on:click="add(5)">ADD 5!</button>
</can-template>
<can-template name="countDisplay">
You have counted to {{count}}!
</can-template>
</my-counter>
Use can-slot to render the passed <can-template>
views or
provide default content if a corresponding <can-template>
was not provided:
Component.extend({
tag: "my-counter",
view: `
<can-slot name="incrementButton"
add:from="this.add">
<button on:click="add(1)">+1</button>
</can-slot>
<can-slot name="countDisplay"
count:from="this.count">
{{count}}
</can-slot>
`,
ViewModel: {
count: {type: "number", default: 0},
add(increment){
this.count += increment;
}
}
});
<!-- Adds the custom element to the page -->
<my-app></my-app>
<script type="module">
import { Component } from "can";
can.Component.extend({
tag: "my-counter",
view: `
<can-slot name="incrementButton"
add:from="add">
<button on:click="add(1)">+1</button>
</can-slot>
<can-slot name="countDisplay"
count:from="count">
{{count}}
</can-slot>
`,
ViewModel: {
count: {type: "number", default: 0},
add(increment){
this.count += increment;
}
}
});
Component.extend({
tag: "my-app",
view: `
<my-counter count:from="5">
<can-template name="incrementButton">
<button on:click="add(5)">ADD 5!</button>
</can-template>
<can-template name="countDisplay">
You've counted to {{count}}!
</can-template>
</my-counter>
`
});
</script>
Data Modeling
Connect data types to a restful service with can-rest-model:
import {restModel} from "can";
import Todo from "//canjs.com/demos/api/todo.mjs";
import TodoList from "//canjs.com/demos/api/todo-list.mjs";
const todoConnection = restModel({
Map: Todo,
List: TodoList,
url: "/api/todos/{id}"
});
Retrieve, create, update and destroy data programmatically:
JavaScript API | Request | Response |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Check the status of request:
const todo = new Todo({ name: "make a model"});
// Return if the todo hasn't been persisted
todo.isNew() //-> true
// Listen to when any todo is created:
Todo.on("created", function(ev, todo){});
let savedPromise = todo.save();
// Return if the todo is being created or updated
todo.isSaving() //-> true
savedPromise.then(function(){
todo.isNew() //-> false
todo.isSaving() //-> false
let destroyedPromise = todo.destroy();
// Return if the todo is being destroyed
todo.isDestroying() //-> true
destroyedPromise.then(function(){
todo.isDestroying() //-> false
})
});
Connect data types to a restful service and have CanJS automatically manage adding and removing items from lists with can-realtime-rest-model:
import {realtimeRestModel} from "can";
// Define a real time restful model
const todoConnection = realtimeRestModel({
Map: Todo,
List: Todo.List,
url: "/api/todos/{id}"
});
// Update instances and lists from server-side events:
var loc = window.location;
const socket = new WebSocket('ws://'+loc.host+loc.pathname+"/ws");
socket.addEventListener("todo-created",(event) => {
todoConnection.createInstance( JSON.parse(event.data) );
});
socket.addEventListener("todo-updated",(event) => {
todoConnection.updateInstance( JSON.parse(event.data) );
});
socket.addEventListener("todo-removed",(event) => {
todoConnection.destroyInstance( JSON.parse(event.data) );
});
Simulate a service layer where you can create, retrieve, update and delete (CRUD) records:
import {fixture} from "can";
let todosStore = fixture.store([
{id: 0, name: "use fixtures", complete: false}
], Todo);
fixture("/api/todos/{id}", todosStore);
Routing
Define routing rules and initialize routing with can-route:
import {route} from "can";
// Create two-way routing rule:
route.register("{page}", {page: "home"});
// Define routing data type
const RouteData = DefineMap.extend({
// Allow undefined properties to be created
seal: false
},{
page: "string"
});
// Connect routing system to an instance
// of the routing data type
route.data = new RouteData();
// begin routing
route.start();
// Provide access to the route data to your application component
Component.extend({
tag: "my-app",
view: `
<page-picker page:from="routeData.page"/>
`,
ViewModel: {
routeData: {
default(){
return route.data;
}
}
}
});
Create responsive links in can-stache views with can-stache-route-helpers:
<a href="{{ routeUrl(page='todos') }}"
class="{{# routeCurrent(page='todos') }}
inactive
{{else}}
active
{{/ routeCurrent}}">Todos
</a>
Utilities
Make AJAX requests with can-ajax:
import {ajax} from "can-ajax";
ajax({
url: "http://query.yahooapis.com/v1/public/yql",
data: {
format: "json",
q: 'select * from geo.places where text="sunnyvale, ca"'
}
}) //-> Promise<Object>
Perform differences with can-diff:
diff.list(["a","b"], ["a","c"])
//-> [{type: "splice", index: 1, deleteCount: 1, insert: ["c"]}]
diff.map({a: "a"},{a: "A"})
//-> [{type: "set", key: "a", value: "A"}]
Read and store the results of feature detection with can-globals:
import {globals} from "can";
globals.getKeyValue("isNode") //-> false
globals.getKeyValue("isBrowserWindow") //-> true
globals.getKeyValue("MutationObserver") //-> MutationObserver
Read and write nested values with can-key:
import {key} from "can";
const task = {
name: "learn can-key",
owner: { name: {first: "Justin", last: "Meyer"} }
}
key.delete(task, "owner.name.first");
key.get(task, "owner.name.last") //-> "Meyer"
key.set(task, "owner.name.first", "Bohdi");
Operate on any data type with can-reflect:
import {Reflect} from "can";
// Test the type:
Reflect.isBuiltIn(new Date()) //-> true
Reflect.isBuiltIn(new Todo()) //-> false
Reflect.isConstructorLike(function(){}) //-> false
Reflect.isConstructorLike(Date) //-> true
Reflect.isListLike([]) //-> true
Reflect.isListLike({}) //-> false
Reflect.isMapLike([]) //-> true
Reflect.isMapLike({}) //-> true
Reflect.isMoreListLikeThanMapLike([]) //-> true
Reflect.isMoreListLikeThanMapLike({}) //-> false
Reflect.isObservableLike(new Todo()) //-> true
Reflect.isObservableLike({}) //-> false
Reflect.isPlainObject({}) //-> true
Reflect.isPlainObject(new Todo()) //-> false
Reflect.isPromiseLike(Promise.resolve()) //-> true
Reflect.isPromiseLike({}) //-> false
Reflect.isValueLike(22) //-> true
Reflect.isValueLike({}) //-> false
// Read and mutate key-value data
const obj = {};
Reflect.setKeyValue(obj,"prop","VALUE");
Reflect.getKeyValue(obj,"prop") //-> "VALUE"
Reflect.deleteKeyValue(obj,"prop","VALUE");
Reflect.assign(obj, {name: "Payal"});
Parse a URI into its parts with can-parse-uri:
import {parseURI} from "can";
parseURI("http://foo:8080/bar.html?query#change")
//-> {
// authority: "//foo:8080",
// hash: "#change",
// host: "foo:8080",
// hostname: "foo",
// href: "http://foo:8080/bar.html?query#change",
// pathname: "/bar.html",
// port: "8080",
// protocol: "http:",
// search: "?query"
// }
Convert a string into another primitive type with can-string-to-any:
import {stringToAny} from "can";
stringToAny( "NaN" ); // -> NaN
stringToAny( "44.4" ); // -> 44.4
stringToAny( "false" ); // -> false
Convert one string format to another string format with can-string:
import {string} from "can";
string.camelize("foo-bar")) //-> "fooBar"
string.capitalize("foo") //-> "Foo"
string.esc("<div>foo</div>")//-> "<div>foo</div>"
string.hyphenate("fooBar") //-> "foo-bar"
string.underscore("fooBar") //-> "foo_bar"