Warning ... this got a lot longer than I expected ...
This needs to be View-Model.
It doesn't need to be as we can add view-helpers that accomplish this. And, it should certainly not be view-model. It should not be adding more visible observables to the system. By "visible observables", I mean: {{@transition.state}}.
@transition is observable data added only due to other observable data mutating. If you find yourself doing this, it means that the VIEW is not able to adequately express something based on the data.
To put this another way, with CanJS's view you should be able to express anything you want from the view-model directly. It currently does a good job of projecting any view-model state into DOM changes. However, it does NOT do a good job of projecting transitions of view-model state into DOM changes.
Ideally, we need an easy way translate changes in view-model state to changes and actions in the view. Consider:
{{#if error}}
<div class="error">{{error}}</div>
{{/if}}
{{#if}} is how we translate a change in state (an error attr being added) to a change in the view (<div/> being added). It also handles the reverse state change - error being removed and the <div/> being removed.
How do we make it easy to also animate that DIV? There are 2 ways. First quick-win:
We make it possible to listen to changes in the VIEW
We can already do this with "inserted". I could make that div animate in like:
<div class='error' can-fadeIn>...
- can.view.Scanner.attr("can-fadeIn", function(data, el){
- $(el).bind("inserted",function(){
- $(this).fadeIn()
- })
- })
Of course, we'd need something different for removed. I'd have a removing event:
- can.view.Scanner.attr("can-fadeOut", function(data, el){
- $(el).bind("removing",function(ev){
- ev.pause()
- $(this).fadeIn(function(){
- ev.resume()
- })
- })
- })
Things get a bit tricker if, for example, you wanted <div> to fadeOut and fadeIn when {{error}} changes. I'd do this with the other strategy
Listening on VIEW-MODEL changes
<div can-fadein can-fadeout can-fadeout-and-in-on="error">
- can.view.Scanner.attr("can-fadeout-and-in-on", function(data, el){
- var attr = el.getAttribute(data.attr)
- var context = data.scope.attr(".")
- context.bind(attr, function(ev, newVal, oldVal){
- $(el).fadeOut(function(){
- $(el).fadeIn()
- })
- })
- })
This example isn't perfect because it:
- Doesn't clean up the event binding
- Would show the new text while it was fading out. This could be corrected by hiding the text node's context with oldVal and updating it with newVal on the fadeIn.
An alternative would be to do something like: <div can-text="error" can-change="fadeInAndOut"> where can-text specifies the text of the div element and can-change listens to when can-text's value changes. Or, with strong DOM modifiers, you could actually do something like I did with removed:
<div can-changing="fadeInAndOut">
On any DOM modification (such as the text changing), we could pause the event, run the fadeOut, resume the event, and run fadeIn.
There's a lot of ways to slice this. CanJS's job is to enable them.