aurelia-contrib/aurelia-knockout

Binding to an AMD view model

AStoker opened this issue · 16 comments

Using your plugin. First off, awesome, love this, since it's the perfect step for me transitioning my Durandal app to Aurelia in stages.
When using this plugin to bind to a view model that is a class, all is well. However, if I try and bind it to a view model that is AMD (the old Durandal style), what is bound is an empty object. Does this plugin require the use of a class to bind, or can it support an AMD module?

I assume that you mean the following when speaking from classes:

    var TestModel = function () {
        this.testText = "John Doe";
    };

    TestModel.prototype.doIt = function () {
        alert("done");
    };

    return TestModel;

and from AMD style?

    var vm = {
        testText: "John Doe"
    };

    vm.doIt = function () {
        alert("done");
    };

Classes that bind correctly are in this syntax:

export class ViewModel{
   constructor(){
      this.foo = 'bar';
   }
}

AMD modules that don't bind correctly (and just have an empty object):

define(function(){
   var foo = 'bar';
   return {
      foo: foo
   }
})

Of course, I do believe Babel transpiles classes to that syntax you provided first, yes.

I can reproduce this bug and try to find a fix.

The problem seems to be that the return value of your AMD module is an object and not a function. If you use "prototype" based syntax you return the constructor function instead of the plain object.

I tried this scenario both with SystemJS and RequireJS as module loaders. Both loaders have problems to load the module accordingly. However, RequireJS and the AMD spec generally support return values with other types than functions.

The easiest way to fix this will be to wrap all your module return values in anonymous functions:

define(function(){
   var foo = 'bar';
   return function() {
     return {
       foo: foo
     }
   }
})

Strange that RequireJS would have failed, since that's the syntax that we used with RequireJS in Durandal (and I think is documented). It's the syntax that I've seen pretty frequently used when using AMD modules.
Is there any way to get this to work with the module returning an object (essentially a singleton)?

I found the issue in my composition logic now. I fixed it temporarily in 0.2.2-beta.1 which is not pushed to npm. I will finally fix it today evening.

Looking at the changes you committed, and it seems that that only addresses the compose binding? In my scenario, I'm actually routing from an ES6/Aurelia page to a AMD module with knockout page via the <router-view>

Yes, my fix only addresses the compose binding. Other compositions which are based on html tags (<router-view>, <my-awesome-subview>, ...) are not handled by this plugin because this is done by aurelia itself as core functionality.

I think that aurelia cannot handle es5 modules which are not returning functions...

Finally fixed in 0.2.2

Gotcha, thanks for that explanation, and the code fix. By any long shot, you don't know where in the aurelia code that creates the view instruction (and needs a function instead of object) is located?

And as a side note (sorry, again not related to this issue exactly), have you tested using the aurelia router to route to an AMD module? I'm trying to get that to work, and it looks like the binding engine doesn't quite account for that, but I wanted to see if you had that setup working.

To your first question:
I think this is placed anywhere in the aurelia-templating module. I guess in one of the view-*.js files. Try to debug your model constructor and backtrack the call stack.

To your second question:
I tested this secario and had a strange behavior. My es5 model looks like this:

define([], function () {
    var vm = {
        thirdModel: "John Doe"
    };
    vm.foo = function () {
        alert("done");
    };
    return vm;
});

The HTML code was bound to the foo function instead of the resulting vm object. I traced back the callstack from aurelia-router but I can't locate the place where the mistake happens...

Thanks for checking too. Yah, it seems that when the view model is empty, the aurelia-templating library (getViewStrategy function) gets the "origin" of the object, which in my case goes back to a core-js object and then ultimately tries to get a corresponding iobject.html file (insert explosion of code here). In either case, it seems related to the aurelia code, not yours. Thank you for your help!

No problem! Yes, I've seen your issue in aurelia-router repository. Sounds really strange.

If you have any things during migration which should be simplified please let me know. Maybe I can improve something. There's also a partial migration guide available as pull request. #440

Perfect, I appreciate it, and will let you know if I find a simplified way of doing things.