javascriptknockout.jsknockout-mapping-pluginknockout-3.0knockout-components

Losing context in knockout component


I am packaging a component as a single AMD module using requirejs following this example: https://github.com/sumitkm/BuildingSpaUsingKO/tree/Part2

The problem is when adding a click event when it calls that function in the viewmodel the self property does not contain the viewmodel and points it to window. What is causing this issue? Btw if they are loaded without requirejs and they are inline this issue is not happening.

enter image description here

Index.html:

     <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Dipping your feet into KnockoutJS Components</title>
    <link href="Content/bootstrap.css" rel="stylesheet" />
    <link href="Content/bootstrap-theme.css" rel="stylesheet" />
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="/">KO Components</a>
            </div>
        </div>
    </div>
    <div class="container body-content" style="padding-top:50px">
        <h2>Dipping your feet into KnockoutJS Components</h2>
        <hr />
        <greeter params='name: " Sumit!"'></greeter>
        <greeter params='name: " Optimus!"'></greeter>
        <greeter params='name: " Bumblebee!"'></greeter>
    </div>
    <footer class="navbar navbar-fixed-bottom">
        <div class="container-fluid">
            <p> &copy; 2014 - Still Learning</p>
        </div>
    </footer>
    <script src="App/boot/require.config.js"></script>
    <script data-main="App/boot/startup" src="Scripts/require/require.js"></script>
</body>
</html>

require.config.js

var require = {
baseUrl: "/",
paths: {
    "bootstrap": "Scripts/bootstrap/bootstrap",
    "jquery": "Scripts/jquery/jquery-1.9.0",
    "knockout": "Scripts/knockout/knockout-3.2.0beta.debug",
    "text": "Scripts/require/text"
},
shim: {
    "bootstrap": {
        deps: ["jquery"]
    }
}
}

startup.js

define(['jquery', 'knockout', 'bootstrap'], function ($, ko) {

    ko.components.register('greeter', { require: 'App/components/greeter/greeting' });


    ko.applyBindings();
});

greeting.html - component template

 <div class='container-fluid'>
    <div> Hello <span data-bind='text: greeting'></span></div>
    <div> It is <span data-bind='text: date'></span></div>

    <input type="button" value="test" data-bind="click: testClick" />
  </div>

greeting.js - component viewmodel

define(["knockout", "text!./greeting.html"], function (ko, greeterTemplate) {

    function greeterViewModel(params) {
        var self = this;
        self.greeting = ko.observable(params.name);
        self.date = ko.observable(new Date());

        self.testClick = function () {
            //self is pointing to window instead of greeterViewModel
            debugger;
        };
    }
    return { viewModel: greeterViewModel, template: greeterTemplate };
});

Solution

  • The JavaScript compiler tries to be smart about closures. Because your function doesn't use any variables from the closure, the function simply doesn't include the closure. Change your code to this and you'll see that it works:

        self.testClick = function () {
            console.log(self);
            debugger;
        };