BorisMoore / jsviews

Interactive data-driven views, MVVM and MVP, built on top of JsRender templates
http://www.jsviews.com/#jsviews
MIT License
856 stars 130 forks source link

Binding to computed functions: onBind callback called twice. If computed function has no setter , "... is not a function" exception thrown #451

Closed AlexxVS closed 3 years ago

AlexxVS commented 3 years ago

Hi, Please take a look at this abstract case:

<body>
    <div id="text1">
    </div>

    <script type="text/javascript">

        $(function() {
            $.views.tags({
                tag1: {
                    template: `<div>{{:~arg1}}</div>`,
                    bindTo: [0, "param1"],
                    linkedCtxParam: ["arg1", "param1"],
                    onBind: function() {
                        console.log("tag1.onBind");
                    }
                }
            });
            $.views.viewModels({
                vm: {
                    getters: ["Prop1"]
                }
            });
            let m = $.views.viewModels.vm.map({Prop1: 1234});
            let tmpl = $.templates("{^{tag1 Prop1() param1=~util1(1) /}}");
            let utils = {
                util1: function(a) { return 1; }
            };
            tmpl.link("#text1", m, utils);
        });

    </script>
</body>

With JsViews versions 1.0.5 and earlier this worked, but starting from 1.0.6 and up to 1.0.9 the "view.ctxPrm(...) is not a function" exception is thrown. Now let's remove "Param1" from bindTo and linkedCtxParam:

<body>
    <div id="text1">
    </div>

    <script type="text/javascript">

        $(function() {
            $.views.tags({
                tag1: {
                    template: `<div>{{:~arg1}}</div>`,
                    bindTo: [0],
                    linkedCtxParam: ["arg1"],
                    onBind: function() {
                        console.log("tag1.onBind");
                    }
                }
            });
            $.views.viewModels({
                vm: {
                    getters: ["Prop1"]
                }
            });
            let m = $.views.viewModels.vm.map({Prop1: 1234});
            let tmpl = $.templates("{^{tag1 Prop1() param1=~util1(1) /}}");
            let utils = {
                util1: function(a) { return 1; }
            };
            tmpl.link("#text1", m, utils);
        });

    </script>
</body>

Now the exception is not thrown, but onBind callback method is called twice. With versions prior 1.0.6 onBind is called only once.

BorisMoore commented 3 years ago

Thank you very much for posting this. It's a good catch. I'll need to do some investigation... I'll let you know...

BorisMoore commented 3 years ago

Here is a potential update that should address your issues. Can you test that it works correctly for you?

jsviews.zip

Incidentally, your example is data-linking to util1(), as if it was a computed observable, but for the binding to work, it needs an associated setter function. See https://www.jsviews.com/#computed@getset.

For example, you could write:

let tmpl = $.templates("{^{tag1 Prop1() param1=~hlp.util1(1) /}}");
let utils = {hlp: {
    util1: function(a) {
      return this._util1;
    },
  _util1: "initUtil1"
}};
utils.hlp.util1.set = function(val) {
  this._util1 = val;
};
tmpl.link("#text1", m, utils); 

If you provide a setter, as above, the "view.ctxPrm(...) is not a function" exception will not be thrown.

But with the jsviews.js update provided there will no longer be any exception, even if you don't provide a setter. (Though of course two-way binding will not work unless there is a setter defined...)

Let me know if this works for you...

AlexxVS commented 3 years ago

Thank you, Boris. The update resolves both throwing the exception and double onBind event.

BorisMoore commented 3 years ago

Thanks for confirming. I except to include this in the upcoming version 1.0.10.

BorisMoore commented 3 years ago

The fix concerns two issues:

BorisMoore commented 3 years ago

Fixed in release v1.0.10