Today, I'm happy to announce the release of CanJS 2.2. Here's the 2.2.4 download, its npm page, and the changelog. This release includes just under 500 commits, a bunch of bug fixes, build improvements, and more. 2.2 is backwards compatible with 2.1 and 2.0. If you do encounter issues, we're on gitter, ready to answer your questions. On Friday, April 10th 2014 we also talked about this release in our Podovi Bitcast. Watch it here:

Unlike 2.0, which introduced can.Component, and 2.1, which introduced can.stache and the define plugin, 2.2 makes less dramatic improvements, but with a wider variety of enhancements.

Here's the top 10 new features we are excited about:

Read on to learn more about each new feature.

1. Browserify and StealJS workflows

If you are using Browserify, install CanJS with:

> npm install can --save

require CanJS to your heart's content:

// main.js var Component = require("can/component/component"), stache = require("can/view/stache/stache"), $ = require("jquery"); Component.extend({ tag: "hello-world", template: stache("<h1>Hello There</h1>") }); $("body").append( stache("<hello-world/>")() );

And then build it:

> browserify main.js > build.js

If you are using StealJS with the npm plugin, you can do the same thing. Install CanJS with:

> npm install can --save

Import your modules:

// main.js import Component from "can/component/"; import stache from "can/view/stache/"; import $ from "jquery"; Component.extend({ tag: "hello-world", template: stache("<h1>Hello There</h1>") }); $("body").append( stache("<hello-world/>")() );

2. can-EVENT Arguments

You can finally call functions from the template with arguments. The following calls the List 's splice method with arguments to remove the clicked item.

JS Bin

It looks like:

<li can-click='{items.splice @index 1}'>

Besides scope values, you can also pass the element ( @element ) and the event object ( @event ).

3. Observable Promises

Promises are now observable in stache! You can ask the state of a promise with promise.isPending , promise.isResolved and promise.isRejected . You can get the resolved value with promise.value and the rejected value with promise.reason .

The following passes a jQuery promise returned by $.get to a template. The template shows "Loading..." until the promise is resolved. Once the promise is resolved, the resolved items are displayed. Hit "Run with JS".

JS Bin

The following example shows how nicely observable promises and the define plugin let you articulate loading / loaded / rejected behavior of related items. Change the "Link Type".

JS Bin

4. Templates in the Page with `can-autorender`

If you add a can-autorender attribute to template script tags, they will be automatically rendered and inserted into the page next to the script tag. It looks like this:

<body> <script id='app' type="text/stache" can-autorender> {{greeting}} {{subject}}! </script> <!-- other scripts needed to load CanJS --> </body>

You can pass values to the template by updating it's view model or by setting attributes on the script tag:

<body> <script id='app' type="text/stache" can-autorender greeting="Hello"> {{greeting}} {{subject}}! </script> <!-- other scripts needed to load CanJS --> <script> $("#app").viewModel().attr("subject","World") </script> </body>

Here's a JSBin showing the behavior:

JS Bin

This is as close as we can get to in-page templates while still maintaing the expressive power of mustache and handlebars. Its a great way to add a component's behavior to your site:

<script type="text/stache" can-autorender> <my-tabs> <my-panel title="Cats">Info About Cats</my-panel> <my-panel title="Dogs">Info About Dogs</my-panel> </my-tabs> </script>

Here's a JSBin showing that off:

JS Bin

5. Template Dependencies with <can-import>

You can now use can-import to declare module dependencies of a template. This allows you to import the components or helpers your template depends on:

<can-import from="my-tabs"/> <my-tabs> <my-panel title="Cats">Info About Cats</my-panel> <my-panel title="Dogs">Info About Dogs</my-panel> </my-tabs>

This works in two places:

Template modules loaded with System loaders (like StealJS)

can-autorender templates in an environment with a System loader or an AMD loader (like RequireJS)

Use with template modules

If you are using StealJS and have configured the ext extension, when you import a template like:

import template from "./templates/tabs.stache!"

any <can-import> s in the template will be imported before processing the template.

If you are using SystemJS, you can use the can/view/stache/system plugin to process stache templates and add this behavior.

If there's demand, we might make a similar plugin for RequireJS.

Use with can-autorender

If you are using can-autorender like:

<script type="text/stache" can-autorender> <can-import from="my-tabs"/> <my-tabs> <my-panel title="Cats">Info About Cats</my-panel> <my-panel title="Dogs">Info About Dogs</my-panel> </my-tabs> </script>

It will use either System.import or an AMD require to import any modules specified by <can-import> . Just make sure you require the can/view/autorender module.

<script src="./require.js"> <script> require.config({ ... }); require(['can/view/autorender']); </script>

Here are the test pages we use for RequireJS and StealJS.

6. Component.scope is now Component.viewModel

A Component's scope property and a template's scope object being named both "scope" has been a constant source of confusion. To help clarify, we have deprecated a Component's scope property and are now calling it a Component's viewModel. This means that if your component looked like:

can.Component.extend({ tag: "my-component", scope: { ... } })

Change it to:

can.Component.extend({ tag: "my-component", viewModel: { ... } })

This change hopefully lets us clear up the following concepts:

template context - The first object that stache or mustache will look within to satisfy key values. For example, looks first on the template context for a "prop" property before continuing up the template scope.

- The first object that stache or mustache will look within to satisfy key values. For example, looks first on the template context for a property before continuing up the template scope. template scope - Represents all the objects that stache or mustache will potentially look within to satisfy key values.

- Represents all the objects that stache or mustache will potentially look within to satisfy key values. component viewModel - a can.Map instance that is used as the context the component's template is rendered with.

7. Define Plugin Improvements

The define plugin has quickly become one of the best parts of CanJS. In 2.2, the nature between getters and setters has been fully fleshed out. The define.get documentation is the best place to get an in-depth understanding of what can be done. However, in short, getter definitions can be asynchronous and update themselves when set. For example, the following shows a getter that updates the person property as personId changes until person is set.

JS Bin

8. Sortable Lists

The sort plugin as been dramatically improved. Simply set a list's "comparator" property and the list will take care of itself.

Look how easy it is to setup sorting different columns and keep them sorted:

JS Bin

When an item changes and needs to be repositioned, the DOM elements are moved and not re-created.

9. Batching Improvements

This issue identified a problem with how events are dispatched within a batch. Previously, additional events triggered within a batch were fired immediately. Now they are queued to be fired at the end of a batch.

Consider the following example:

var map = new can.Map(); map.bind("a", function(){ map.attr("b",1) }) can.batch.start() map.attr("a", 1) map.attr("c",1) can.batch.stop()

Previously, the event order was "a","b","c". Now it will be "a","c","b".

This fix highlighted some problems in some of our applications that were not "batch" ready as they should have been. These components were doing things like setting up their own list live-binding behavior. They would read the list, write out DOM elements, and then immediately bind to "add" and "remove" events. Something like this:

liveBindList = function(list, el, renderer){ list.each(function(item){ el.appendChild( renderer(item) ) }); list.bind("add", function(el, items){ can.each(items, function(item){ el.appendChild( renderer(item) ) }); }); }

The problem is that liveBindList could be called within a batch like:

var list = new can.List(); can.batch.start(); list.push(["A","B"]) liveBindList( list ) can.batch.stop();

When liveBindList is called, list looks like ["A", "B"] , but there is a queued "add" event that hasn't been fired yet.

To solve this, we added in 2.2.3, an undocumented can.batch.afterPreviousEvents function that lets you know when all previous batched events have finished dispatching. This API may change for 2.3. But for now, we could change the previous example to:

liveBindList = function(list, el, renderer){ list.each(function(item){ el.appendChild( renderer(item) ) }); can.batch.afterPreviousEvents(function(){ list.bind("add", function(el, items){ can.each(items, function(item){ el.appendChild( renderer(item) ) }); }); }) }

10. leakScope

Last but not least, can.Component:leakScope makes can.Components a bit more deterministic and well behaved. By default, a can.Component's template can reference a parent component's viewModel values. For instance, if I have the following:

can.Component.extend({ tag: "house-sale", template: "<h2>House Sale</h2><offer-price/>", viewModel: {showing: "April 18th, 2015"} }) can.Component.extend({ tag: "offer-price" template: "" })

If I add a <house-sale> to the page, I get the following:

JS Bin

This is because <offer-price> is able to lookup in <house-sale> 's viewModel. A component's outer scope "leaks" into a component's template. Sometimes, this is useful. Other times, this can be confusing or difficult to debug. Consider what happens if <offer-price> 's showing property was indented to toggle a price to be shown like:

can.Component.extend({ tag: "offer-price" template: "{{#if showing}}<p>Offer Price: {{price}}</p>{{/if}}" })

<offer-price> 's outer scope might happen to have a truthy showing property and cause the offer price to show up unexpectedly. In the following example, I indented the showAskingPrice to toggle <offer-price> 's showing property. However, the offer price is shown and stays that way.

JS Bin

The solution is to add leakScope: false to <offer-price> like:

can.Component.extend({ tag: "offer-price" template: "{{#if showing}}<p>Offer Price: {{price}}</p>{{/if}}", leakScope: false })

This will keep the price hidden no matter what happens in the outside scope. The only way to show the price is to explicitly pass a showing value to <offer-price> like:

<offer-price price="{asking}" showing="{showAskingPrice}"/>

As shown here:

JS Bin

Conclusion

2.2 has made a lot of common patterns and practices easier to articulate. We're hoping to get everyone to upgrade to it soon. If you are on an old version of CanJS (or StealJS), look out for a special announcement soon.

For 2.3 and 3.0 we are going to return to introducing some flashy features, starting with the remaining features originally planned for 2.2. We already have a very good start on a new getting started guide. We'll be releasing a roadmap shortly.