aurelia-contrib / aurelia-knockout

Adds support for Knockout binding syntax to make transition from Durandal and Knockout to Aurelia simpler.
MIT License
22 stars 4 forks source link

Binding to an AMD view model #4

Closed AStoker closed 8 years ago

AStoker commented 8 years ago

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?

ckotzbauer commented 8 years ago

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");
    };
AStoker commented 8 years ago

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
   }
})
AStoker commented 8 years ago

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

ckotzbauer commented 8 years ago

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

ckotzbauer commented 8 years ago

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
     }
   }
})
AStoker commented 8 years ago

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)?

ckotzbauer commented 8 years ago

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.

AStoker commented 8 years ago

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>

ckotzbauer commented 8 years ago

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...

ckotzbauer commented 8 years ago

Finally fixed in 0.2.2

AStoker commented 8 years ago

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?

AStoker commented 8 years ago

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.

ckotzbauer commented 8 years ago

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...

AStoker commented 8 years ago

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!

ckotzbauer commented 8 years ago

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

AStoker commented 8 years ago

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