1

Taking the following code snippet as a quick example:

var Animal = Backbone.Model.extend();
var Zoo = Backbone.Collection.extend({ model: Animal });

var tiger = new Animal({ name: "tiger" });
var zoo = new Zoo(tiger);

var viewModel = {
    tiger: kb.viewModel(tiger);
    zoo: kb.collectionObservable(zoo);
}

ko.applyBindings(viewModel);

from the $data context you can get a reference to the tiger model:

tiger === $data.tiger().__kb.object;

or

tiger === $data.zoo()[0].__kb.object;

and I assume it exists somewhere on this dependantObservable function, but I can't seem to find the reference to the original Backbone Collection

$data.zoo

Does anyone have any idea of how to get at the original Backbone Collection?

Also, bonus points if you can tell me of any way to get at the Backbone Collection if the viewmodel is this instead:

viewModel = kb.collectionObservable(zoo)

the challenge here is that $data contains the results of the evaluated dependantObservable function.


EDIT After receiving a perfectly valid answer to the question above I realized that my problem only occurs in my more complicated binding with nested templates:

The templates look like this:

    <!-- outer template -->
    <script type="text/html" id="tmpl-outer">
        <button data-bind="click: $root.outerContext">Outer Context</button>
        <div data-bind="template: { name: 'tmpl-inner', data: collection }"></div>
    </script>

    <!-- inner template -->
    <script type="text/html" id="tmpl-inner">
        <button data-bind="click: $root.innerContext">Inner Context</button>
        <div data-bind="foreach: $data">
            <button data-bind="click: $root.modelContext">Model Context</button>
        </div>
    </script>

Model and View-Model:

var model = new Backbone.Model();
var collection = new Backbone.Collection(model);

var viewModel = {
    collection: kb.collectionObservable(collection),
    outerContext: function (data) {
        console.log(data.collection.collection() === collection);
    },
    innerContext: function (data) {
        console.log("??????? === collection");
    },
    modelContext: function (data) {
        console.log(data.model() === model);
    }
};

ko.applyBindings(viewModel);

And finally, somewhere to render everything:

<body>
    <div data-bind="template: { name: 'tmpl-outer' }"></div>
</body>

So, my initial question that I over-simplified my example for should have been: how do I get at the underlying collection on the line:

console.log("??????? === collection");

It appears that the collection in this context has been converted to a simple KnockOut observable array - there doesn't seem to be any of the important KnockBack properties.

Clint
  • 1,159
  • 1
  • 10
  • 19
  • If there's a model in the collection you can of course get at the collection via the model's reference to its collection as below, but this is certainly not ideal as it relies on the collection being non-empty: `zoo === $data.zoo()[0].__kb.object.collection;` – Clint Jul 16 '14 at 05:00
  • Ah I see... Could you not make the view model a function giving you the ability to call public/private methods/properties inside that click event? As for actually solving the problem, I think I'd need to see at least this portion in action. – David Barker Jul 21 '14 at 08:33
  • Yes - absolutely, that would normally be the logical way to solve this... I suspect I might be trying to do something illogical though. My main aim was to be able to write a generic method that could add a new model to the collection (usable by one of many different types of nested collections). I've posted my current solution to this below, though I'm not really 100% happy with it because it involves a level of indirection by having to get the collection from the parent element. – Clint Jul 22 '14 at 00:14

2 Answers2

2

You can get the underlying collection / model by using the getters on instances of kb.CollectionObservable and kb.ViewModel.

var collection = new Backbone.Collection(),
    view_models = kb.collectionObservable(collection),
    reference = view_models.collection();

console.log(collection === reference);

You can do the same with instances of kb.viewModel

var model = new Backbone.Model({ id : 1 }),
    view_model = kb.viewModel(model),
    reference = view_model.model();

console.log(model === reference);

You can access the collection/model as well from $data by calling the getters in the data-binds, though I really can't see any need at all to do this if you use factory view_models for the collection allowing you to define any number of specific computeds / observables for each vm.

var model = new Backbone.Model({ id : 1 });

var collection = new Backbone.Collection(model);

var AnimalViewModel = kb.ViewModel.extend({
    constructor: function(model) {
        kb.ViewModel.prototype.constructor.call(this, model, {});
        return this;

        // Custom code per vm created
    }
});

var view_model = {
    zoo : kb.collectionObservable(collection, {
        view_model : AnimalViewModel
    });
}
David Barker
  • 14,484
  • 3
  • 48
  • 77
  • Hi David - thanks, this is indeed a perfectly valid answer to the question I asked. My problem is that I've got a slightly more complicated scenario where this does not appear to work - I've extended my question above to detail this issue. – Clint Jul 21 '14 at 00:58
  • If you want to access the model() getter via a KO binding context (like, say, delete a model from a click binding), you **MUST** call *kb.ViewModel.prototype.constructor.call(this, model, {});* in your kb.ViewModel constructors. Thanks, David! – Kyle Hale Nov 07 '14 at 15:34
0

In the end I found that I had to go via the parent to get the collection. I don't like this level of indirection, but I can't find any way around it.

The view-model now has this function in it:

doSomethingWithUnderlyingCollection: function(collectionName, parentContext) {
    var underlyingCollection = parentContext.model().get(collectionName);

    // do something with the underlying collection here, e.g. add a model.
}

And then to call the method from the template:

<button data-bind="click: function() { $root.doSomethingWithUnderlyingCollection('MyCollection', $parent); }">Add</button>
Clint
  • 1,159
  • 1
  • 10
  • 19