Capturing appViewModel events in CanJS 4


#1

HI All,
Another question related to a CanJS 3 to CanJS 4 migration.

I have a simple appViewModel defined and linked to the router

import DefineMap from 'can-define/map/';
var AppViewModel = DefineMap.extend('AppViewModel',{seal:false},{
    // page for routing
    page: {
      type: 'string',
      default: 'dashboard',
    },
});
export default new AppViewModel({});

Then the router is

import Construct from 'can-construct';
import route from 'can-route-pushstate';

import appViewModel from './app-viewmodel';

export default Construct.extend({
  init: function() {
    route.data = appViewModel;
    route.register('{page}', {page: 'dashboard'});
    route.register('things/{thing}/settings', {page: 'thing_settings'});
    route.start();
  },
});

I have a component that gets rendered when the route matches “things/{thing}/settings”. Now in the component, I want to load a different thing when the ‘thing’ changes in the appViewModel.

In the controller, i have:

export default Component.extend({
  tag: 'page-thing',
  events: {
    '{appViewModel} thing': function(avm,el,value) {
        // refresh the current view
        this.viewModel.refresh();
    }
  },
  ViewModel: ViewModel,
  view: view
});

In the component viewmodel, i have something like:

import DefineMap from 'can-define/map/';
import appViewModel from 'app/app-viewmodel';
export default DefineMap.extend({seal:false},{
  refresh: function() {
     .. load data from backend etc ..
  },
  appViewModel: function() {
     return appViewModel;
  },
});

In CanJS3, when “thing” changed in the route and the appViewModel, the event triggered in the thing controller and the page was refreshed. In CanJS 4, the event doesn’t trigger, and there is an error message in the console

Potentially unhandled rejection [1] Error: can-event-queue: Unable to bind thing

I tried to change the controller to

  events: {
    '{appViewModel()} thing': function(avm,el,value) {
        // refresh the current view
        this.viewModel.refresh();
    }
  },

But this didn’t work either (event not captured, but no error in the console log now)

I also tried

'{viewModel} appViewModel.thing': function(avm,el,value) {

'{viewModel} appViewModel().thing': function(avm,el,value) {

'{viewModel} appViewModel': function(avm,el,value) {

'{viewModel} appViewModel()': function(avm,el,value) {

These all resulted in no event and no errors.

I also went back to simply using

appViewModel: appViewModel,

in the viewmodel (instead of the function). CanJS3 didn’t like this, which is why I changed to the function, but it doesn’t give an error in CanJS4. This didn’t help.

Any idea how I should listen to events on the appViewModel? The documentation that I can find only talks about using {viewModel} in the component events.

Sorry for pasting code rather than using JS Bin, but we are using StealJS and producing an example running in JS Bin would take some time (I started but gave up)

This is the only major problem, I’ve come across so far, so I consider that the migration is going well!

Cheers

Rob


#2

Rob, can you use a getter instead of a function?

  get appViewModel() {
     return appViewModel;
  },

Then:

{appViewModel} thing

should work. Let me know if not.


#3

This problem is likely due to us removing implicit function calling in CanJS 4. Component event literals ("{appViewModel} thing") aren’t processed like stache, so they lack the ability to call functions now. This was a consequence that I didn’t foresee.


#4

Here’s a JSBin showing it’s working: https://justinbmeyer.jsbin.com/goqadal/edit?html,js,output

Also, you might want to migrate away from “events” and towards connected callback: https://justinbmeyer.jsbin.com/pozolon/2/edit?html,js,output

  ViewModel: {
    isRefreshed: {default: false},
    get appViewModel(){
      return appViewModel;
    },
    connectedCallback() {
      this.listenTo(appViewModel, "thing", ()=>{
        this.isRefreshed = true;
      })
    }
  }

Or for this example, the new value() resolver: https://justinbmeyer.jsbin.com/wozukaz/1/edit?html,js,output

  ViewModel: {
    isRefreshed: {
      value({resolve, listenTo}) {
        resolve(false);
        listenTo(appViewModel, "thing", ()=>{
          resolve(true);
        })
      }
    },
    get appViewModel(){
      return appViewModel;
    }
  }

#5

Hi Justin,

Thanks! Changing the viewmodel to

get appViewModel(){
     return appViewModel;
},

has made it work again. I’m not sure I totally understand why though - I will have to read up on the getters and see if we should be using them elsewhere.

It would probably be a good idea to to migrate away from “events” and towards connected callback. I’m using a connectedCallback to get a component “inserted” event when I need one. Its a big app already, about half developed, so I wanted to migrate to CanJS4 before changing that stuff.

Thanks once again for your quick response

Rob


#6

@justinbmeyer I don’t get that syntax (using the curly braces) is it a canjs thing or an ES6 feature?


#7

ES6 object destructuring


#8

But I’ve only seen this on assignments (see example below). The quoted code, has a { appViewModel() } thing as a property name (or function name) so I don’t really get it. What would be the equivalent in “old” javascript?

Thanks

var { foo, bar } = { foo: "lorem", bar: "ipsum" };

#9

@nriesco get appViewModel() is JavaScript’s getter syntax, nothing special to CanJS: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get

@robfoweraker I think the original code didn’t work because appViewModel was defined as a method on your view-model, not a property. I think this also would have worked:

appViewModel: {
  default: function() {
    return appViewModel;
  }
}

#10

@chasen I really don’t get the syntax.

I see this:

 '{appViewModel()} thing': function(......) {
    ...
}

and I don’t get it. It looks like a name to me but it has a weird syntax.


#11

Ah, that was one of the things Rob tried but it didn’t work. That’s not something that CanJS understands.


#12

@chasen I’ve seen this syntax before: {appViewModel} thing for instance here: https://canjs.com/doc/can-component.prototype.events.html

So my question is: 1. are the curly braces there something that is particular to canjs? If not, how can I rewrite this (just to understand it) into old/simple no ES6 javascript? and 2. what exactly does it mean? that instead of listening to an event it will listen to changes to that “variable” within the viewmodel?

Thanks a lot!


#13

Ah sorry I didn’t understand your question!

  1. Yes, the curly braces are part of can-control’s event syntax.

  2. For DOM events like ".next click" we can just use addEventListener; for CanJS’s observables, we can listen to when their values (or keys) change. can-event-queue’s on docs provide a little more info on how this works.