1
votes

Hello I have a KnockoutJs computed observable ItemCountText in an extender "paged" that extends an observableArray. The observableArray is Products.

This works as expected, giving me "32 Products" (32, 48, whatever the number is, plus "Products" or whatever text I passed into the extender's settings)

<span data-bind="text:Products.ItemCountText"></span> 

.. But this

<!-- ko with: Products -->
<span data-bind="text:ItemCountText"></span>
<!-- /ko -->

... is broken with Error: Unable to parse bindings. Message: ReferenceError: ItemCountText is not defined; Bindings value: text: ItemCountText

What might be the problem here that causes the extender's computed observable ItemCountText not to be defined within "with: Products" although a "Products.ItemCountText" binding is defined? It feels as though the binding mechanism for 'ko with:' cannot find its way 'into' the extender.

Here is a fiddle demonstrating the issue. To avoid distractions, the paged extender has everything snipped out that is not relevant to the issue.

http://jsfiddle.net/J4Nd2/1/

To experience the issue described above, uncomment HTML line 14 of the fiddle and Run

Knockout is version 2.2.1

Here is the JsFiddle's HTML

    <div id="bindMe">
    <span data-bind='text: Yay()'></span>
    <div data-bind="text: Products.ItemCountText"></div>
    <div><span data-bind="text: Products().length"></span> on this page</div>
    <!-- ko with: Products -->
        <ul>
            <!-- ko foreach: $data -->
                <li data-bind="text: $data"></li>
            <!-- /ko -->
        </ul>
    <!-- /ko -->
<span data-bind='text: Boo'></span>
    <!-- ko with: Products -->
<!--    <div data-bind="text: ItemCountText"></div>  -->
        <div><span data-bind="text: length"></span> on this page</div>
        <ul>
            <!-- ko foreach: $data -->
                <li data-bind="text: $data"></li>
            <!-- /ko -->
        </ul>
    <!-- /ko -->   
</div>

Here is the JsFiddle's Javascript

ko.extenders.paged = function (target, options) {
    // Settings
    var settings = $.extend({
        "itemcounttext": 'Counted Items'
    }, options);

    target.TotalRecords = ko.observable();
    target.ItemCountText =  ko.computed(function () {
        return target.TotalRecords() + " " + settings.itemcounttext;
    });

    return target;
};
/// -- End paging extender -- \\\

var ViewModel = function()  {
    var self = this;
    self.Yay = ko.observable("Hello World... Products.ItemCountText.. it works great");
self.Boo = ko.observable("But if HTML line 14 is uncommented, why does ItemCountText from extender broke using ko with?");
    self.Products = ko.observableArray([]).extend(
    {
        paged:
        {
            "itemcounttext": 'Products in total'
        }
    });
};

var myViewModel = new ViewModel();
ko.applyBindings(myViewModel, document.getElementById("bindMe"));

myViewModel.Products.push('prod a');
myViewModel.Products.push('prod b');
myViewModel.Products.push('prod c');
myViewModel.Products.TotalRecords(5);
2

2 Answers

0
votes

The problem is that your observable is created and bound to the view before your data was actually in that ItemCountText -

http://jsfiddle.net/J4Nd2/2/

<div data-bind="text: $data.ItemCountText"></div>

By appending $data. on the front, it tells Knockout that only bind this if it has a value, basically deferring binding until ItemCountText has a value.

Edit update

Your with: binding is ignoring the extended properties because you are binding the context to the value of the array, which doesn't include those extended properties.

<!-- ko with: Products -->
<span data-bind="text: $data"></span>

When you are binding with: products it is almost the same as binding with the value of an observable - any other properties of that object that are not part of the observable value are ignored.

If you must bind within the containerless with binding use $parent.Products to get it back in - http://jsfiddle.net/J4Nd2/4/

0
votes

After failed experimentation with custom binding where I still couldn't access the properties of an extender, I uncovered a rather simple solution of declaring a sort-of pointer to the 'paged' extender

Products and Changes are both observableArrays extended with an extension named 'paged'

self.Products.Paged = ko.computed(function() {
    return self.Products['extend']('paged');
});

self.Changes.Paged = ko.computed(function() {
    return self.Changes['extend']('paged');
});

Now I can use the containerless 'with' binding to contain my _Pager view that uses properties of my extender named 'paged', and now anything extended by 'paged' can be bound to my _Pager view

<!-- ko with: Products.Paged -->
    @Html.Partial("_Pager",Model)
<!-- /ko -->

and

<!-- ko with: Changes.Paged -->
    @Html.Partial("_Pager",Model)
<!-- /ko -->

Now that I have uncovered this, I bet a custom binding could be written to do the pointing such that the pointer need not be added to everything extended. Instead, a custom binding named "withExtension" might accept the name of the extension, allowing for