Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When $parent.foo is a prototype method, the this is not the parent, workaround?

Tags:

knockout.js

In my view model I have prototype methods where the this context is the item in the for each, not the actual view model. How do I achieve this?

  <button data-bind="click: $parent.methodThatNeedsAccesToTheParentViewModel">Edit</button>

ViewModel:

ViewModel.prototype.methodThatNeedsAccesToTheParentViewModel= function () {
        this = duff //this is the item in the foreach, not the real parent
    };

Ideas?

like image 220
FutuToad Avatar asked Mar 06 '13 16:03

FutuToad


People also ask

When should we use prototype method?

It is highly recommended to use Prototype Method when the object creation is an expensive task in terms of time and usage of resources and already there exists a similar object. This method provides a way to copy the original object and then modify it according to our needs.

What is an object’s prototype?

An object’s prototype is a private property that each object has that references or points to its prototype (the object that it inherits from). According to the JavaScript documentation at ECMAScript, this internal property is called [ [Prototype]].

What is the prototype of null?

By definition, null has no prototype, and acts as the final link in this prototype chain. Nearly all objects in JavaScript are instances of Object which sits on the top of a prototype chain.

Why do objects inherit properties from their prototype?

This is because objects can inherit properties from their prototype. The object that another object inherits properties from is called its prototype. If you need a mental model, you can think of how a child inherits characteristics from its parent. In this example, the parent would be considered the ‘prototype’ and the child the ‘object’.


2 Answers

Do not pass the function directly to the click binding, wrap it in an anonymous function to have it called in context of your view model:

<button data-bind="click: function() { $parent.methodThatNeedsAccesToTheParentViewModel() }">Edit</button>

this way when the binding is activated it will use $parent as context of the call - i.e. this

It is worth noting (since we can't see the structure of the file bindings) that the $parent variable will refer to the binding preceding the current item being iterated, which is not necessarily the parent of the item in the object graph.

Edit: i was missing some parenthesis above...

Edit 2: this works due to the way the binding is resolved. When the binding is applied by knockout the expression is resolved and must evaluate to a function (and hence why when you bind directly to a function there is no need for the parenthesis). This function is then called when the click binding is activated.

But here's the catch, since the this keyword in prototypal javascript is not bound by the owner of the function (i.e. where it is defined), but rather upon the way it is called (as explained better in mozilla documentation) in this case knockout call the function not through the object that owns it (which would correctly bind the this operator), but binding it explicitly to its current binding context (since it cannot possibly know which instance the function was taken originally). You can replicate this behaviour by getting a reference to your function and then binding it to another object at call time - an example:

A = function() {
   this.name = "foo";
};
A.prototype.getName = function() { return this.name; };

a = new A();

console.log(a.getName()); // should log "foo" as expected

f = a.getName; // now f contains a reference to the function

console.log(f()); // this call the function in the current scope - most likely the window, and return nothing

b = new Object(); // another generic object - suppose this is knockout binding context

console.log(f.call(b)); // this bind the call explicitly to b, but since b has no name property,  nothing is returned again

console.log(f.call(a)); // this time we bind the function call to an object that has a name proerty, so the correct value is returned

On the other hand, by creating an anonymous function you are calling your method explicitly in the scope of the wanted object ($parent), so this is bound correctly.

I hope that helps a bit... :)

like image 160
Grim Avatar answered Dec 08 '22 00:12

Grim


I had the same problem, and I resolved it in this way:

<button data-bind="click: $parent.methodThatNeedsAccesToTheParentViewModel.bind($parent)">Edit</button>
like image 21
Anton Avatar answered Dec 07 '22 22:12

Anton