Question

I just started a new Hot Towel SPA project and I am having trouble doing binding with knockout from the ViewModel.

My view model looks like this:

define([
    "services/logger",
    "knockout"
],
function (logger, ko) {
    var title = ko.observable("Partners");


    return {
        title: title,
        activate: function () {
            var that = this;
            logger.log("Partners view activated", null, "admin.partners", false);

            return true;
        }
    };
});

The view looks like this:

<section class="row-fluid">
    <div class="span10" id="admin-content">
        <h2 class="page-title" data-bind="text: title"></h2>
    </div>
</section>

But the title always ends up being garbage (i.e. the code of the function, like when using the debugger: FUNCTION D(){IF(0<ARGUMENTS.LENGTH) [...])

If I change the binding to "data-bind="text: title()", it works, but I realized that this doesn't bind the observable, only the value. So when doing forms with bindings like these, it doesn't update the observable, and I can't save the value.

I found examples that seem to use the exact same code as me, but I don't understand why it doesn't work.

Was it helpful?

Solution

Christian - You figured it out yourself. I'll elaborate.

Look at the vendor bundle defined in App_Start/bundleconfig.cs and then look at the script loading near the bottom of index.cshtml (~line 29).

You'll see that all 3rd party scripts - including the knockout script - are loaded together ... before Require. That means that none of the 3rd party scripts can detect the forthcoming use of require. Therefore, they load themselves into the global namespace (window).

When RequireJS comes along, it does not know about any of these services either. So when you ask for 'ko' as a dependency, it returns null ... as you can see for yourself if you put a breakpoint where your function begins.

All of this is by design.

You can shim these services into require's equivalent of an IoC container. If you did so, require would find 'ko' and your function would work. You can begin to learn about this here. I've done it. It's not too bad.

But some of us who have been swimming in these waters for a while have decided it's too much of a PITA. So we follow Durandal's simplifying recommendation: "Load a few 3rd party libraries outside of Require and let them litter the global namespace; your more numerous application files should live within Require."

It is not that hard to choose otherwise. Just learn to shim require in your main.js and you'll be in business.

OTHER TIPS

The problem was found with help from the Durandal developer here: https://groups.google.com/forum/#!topic/durandaljs/Ku1gwuvqPQQ

It seems that using AMD to include knockout was the problem. I don't know the specifics, but it probably has something to do with the fact that Durandal uses the global ko variable, while my view models used the ko variable, that was a different one because RequireJS created its own instance.

Can you try something like this:

define([
    "services/logger",
    "knockout"
],
function (logger, ko) {
    var title = "Partners";


    return {
        title: ko.observable(title),
        activate: function () {
            var that = this;
            logger.log("Partners view activated", null, "admin.partners", false);

            return true;
        }
    };
});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top