Skip to content

Instantly share code, notes, and snippets.

@justinbmeyer
Last active December 28, 2015 10:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save justinbmeyer/7487805 to your computer and use it in GitHub Desktop.
Save justinbmeyer/7487805 to your computer and use it in GitHub Desktop.
title tags author lead layout
CanJS 2.0 Release Notes
open-source canjs
justinbmeyer
Today we're happy to announce the latest major release of CanJS.
post

CanJS 2.0 is out, and ready to make JavaScript application development even easier. It brings:

  • Two way binding - <input can-value="person.name"/>
  • In-template declarative event handlers - <button can-click="destroy">X</button>
  • Custom elements - <ui-grid>
  • Mustache and observable performance improvements.

All of this is packaged in a new module that makes it easy to make modular, testable, state-driven widgets and even easier to use them. That module is:

can.Component

This article goes over how to use these new features. Before we show them off, a huge thanks to the Bitovi crew, for helping us with the new features, bug fixes, and getting this out the door.

Also, checkout the release notes and migration guide.

To get CanJS 2.0, go to the downloads page.

Lets see what can.Component can do!

Beautiful APIs

can.Component adds rich behavior to custom elements. It makes it possible for non-developers (designers) to use your components without knowing JavaScript. For example, they can use a tabs widget like:

<tabs>
  <panel title="Fruits">oranges, apples</panel>
  <panel title="Breads">pasta, cereal</panel>
  <panel title="Sweets">ice cream, candy</panel>
</tabs>

And if they know some mustache, a dynamic tabs widget looks like:

<tabs>
  {{#each foodTypes}}
    <panel title='title'>{{content}}</panel>
  {{/each}}
</tabs>

If you add too foodTypes a new panel is added automagially:

<iframe width="100%" height="300" src="http://jsfiddle.net/qYdwR/1019/embedded/" allowfullscreen="allowfullscreen" frameborder="0">demo</iframe>

Ease of development

can.Component conveniently assembles the common features of modern widget construction:

  • A template
  • An observable scope
  • Mustache helpers
  • Event bindings

And it adds things like two-way live binding declarative binding in templates. Lets checkout the basics with some examples.

The following simply inserts <h1>Hello World</h1> whenever it finds a <hello-world></hello-world> element:

can.Component.extend({
  tag: "hello-world",
  template: "<h1>Hello World</h1>"
})
<iframe>hello world</iframe>

A component's template is inserted directly within the custom element. For example, even if the source html looked like:

<hello-world> Keep Me!!! </hello-world>

The template would overwrite the source content within <hello-world>, resulting in:

<hello-world><h1>Hello World</h1></hello-world>

But we can place that source content within the component's template with the <content> tag like:

can.Component.extend({
  tag: "hello-world",
  template: "<h1><content/></h1>"
})

Now the html ends up as:

<hello-world><h1> Keep Me!!! </h1></hello-world>

Provide default content within the <content> tag that gets used if there's no source content. The following says "Hello World" if no source content is provided:

can.Component.extend({
  tag: "hello-world",
  template: "<h1><content>Hello World</content></h1>"
})

To make these templates powerful, they need some observable data. The template is rendered with the component's scope. The following makes <todo-list> elements show a list of todo elements:

 can.Component.extend({
	tag: "todo-list",
	template: 
		"<ul>"+
			"{{#each todos}}"+
				"<li>{{name}}</li>"+
			"{{/todos}}"+
		"</ul>",
	scope: {
	    todos: new can.List([
	      {name: "Take out trash", completed: true},
	      {name: "Mow Lawn", completed: false}
	    ])
	}
})   
<iframe>list</iframe>

The scope defines an observable [can.Map] that is used to render the component's template. In the example above, the scope object is used to define a [can.Map] constructor function like:

TodoListMap = can.Map.extend({
  todos: new can.List([
    {name: "Take out trash", completed: true},
    {name: "Mow Lawn", completed: false}
  ])
})

When a <todo-list> element is found, a new TodoListMap is created and used to render the template. This means that todos is actually a default value and can be overwritten. To overwrite default values or add a property to the todo-list's scope, add an attribute to the element specifying the value.

For example, if a template is passed a list of critical todos like:

var template = can.view.mustache("<todo-list todos='criticalTodos'></todo-list>")

var criticalTodos = new can.List([
  {name: "Learn can.Component", completed: false},
  {name: "Tweet about this article", completed: false}
])

template({
  criticalTodos: criticalTodos
})

Specify the todos property on todos-list's scope like:

<todo-list todos='criticalTodos'></todo-list>

Or more commonly, criticalTodos is part of the parent scope of another component:

var template = can.view.mustache(
  "<todo-app>"+
    "<todo-list todos='criticalTodos'></todo-list>"+
  "</todo-app>");

can.Component.extend({
  tag: "todo-app",
  scope: {
    criticalTodos: new can.List([
     {name: "Learn can.Component", completed: false},
     {name: "Tweet about this article", completed: false}
    ])
  }
})

template()

A list of todos is rarely static. Instead, it might be retrieved by can.Model.findAll when <todo-app> is inserted into the document:

can.Component.extend({
  tag: "todo-app",
  scope: {
    criticalTodos: []
  },
  events: {
    "inserted": function(){
      var scope = this.scope;
      Todo.findAll({critical: true},function(todos){
        scope.attr("criticalTodos", todos)
      })
    }
  }
})

Two Way Binding

Add can-value="key", where key is a reference to an observable value, to an input, select, or textarea to cross-bind the value of that input to the observable value. The following cross-binds the input's value to the person's name:

var person = new can.Map({name: "Sarah"})
var template = can.view.mustache(
  '<h2>{{name}}</h2>'+
  '<input can-value="name"/>'
)

$("#out").html( template(person) )
<iframe>name</iframe>

When the input changes, it will update person's name property. If you update person's name property like:

person.attr('name',"Sarah Brzezinski");

The input's value will update.

We can use this in todo-list's template to update the completed property of each todo by adding a checkbox with can-value set to the completed property:

 can.Component.extend({
	tag: "todo-list",
	template: 
		"<ul>"+
			"{{#each todos}}"+
				"<li class='{{#completed}}completed{{/completed}}'>"+
				  "<input type='checkbox' can-value='completed'/> "+
				  "{{name}}"+
				"</li>"+
			"{{/todos}}"+
		"</ul>"
})  

In template declarative event handlers

That's a mouthful, but this allows you to bind functions in Mustache's scope to DOM events like click, keypress, etc.

The following makes <hello-world> toggle what's in the <h1> everytime someone clicks it.

can.Component.extend({
  tag: "hello-world",
  template: 
    "<h1 can-click='toggle'>"+
      "{{#visible}}"+
        "<content>Hello World</content>"+
      "{{/visible}}"+
    "</h1>",
  scope: {
    visible: true,
    toggle: function(){
      this.attr("visible", !this.attr("visible"))
    }
  }
})

We can use this to add a delete buttons to <todo-list> that destroy the todo on the server:

 can.Component.extend({
	tag: "todo-list",
	template: 
		"<ul>"+
			"{{#each todos}}"+
				"<li class='{{#completed}}completed{{/completed}}'>"+
				  "<input type='checkbox' can-value='completed'/> "+
				  "{{name}}"+
				  "<button can-click='destroy'>X</button>"+
				"</li>"+
			"{{/todos}}"+
		"</ul>"
})  

This works because click event's are wired to each todo's [destroy] event.

Performance improvements

As Mustache is now included in core by default, we've been working on improving rendering performance.

[PERFORMANCE INFO HERE]

Other things to checkout

  • can.Component's docs for a bunch of examples.
  • Our roadmap
  • A can.Component TodoMVC example
@moschel
Copy link

moschel commented Dec 18, 2013

  • why can.component? what is the advantage of building widgets this way
  • compare/contrast with can.control. when is it appropriate to use one or the other
  • why aren’t declarative event handlers “bad”
  • does any of this use event delegation?
  • why is it all exciting and why does it make can unique?
  • mention future compatibility with web components
  • link to changelog
  • talk higher level about canjs mentality (modular design) and how you can achieve that with components

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment