Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limit foreach loop knockout

I have this knockout mapping my array retreived from the ajax call below.

function InvoiceViewModel(data) {
var self = this;

self.survey = data;

}

Ajax Call

$.ajax({
    url: 'http://localhost:43043/api/damage',
    type: 'GET',
    headers: { 'Accept': 'application/json' },
    data: {
        orderNumber: num,
        category: cat
    },
    success:
           function (data) {
               var usingRoutData = document.URL;
               ko.applyBindings(new InvoiceViewModel(data));
           },

    error: function () {
        alert('failure');
    }

});

My Array

 var test = {
  Name: Blah,
  Attributes: [
               {Name: Test, Type: Photo, Year:1988},
               {Name: Test, Type: Photo, Year:1988},
               {Name: Test, Type: Photo, Year:1988}
              ]
            };

How I am binding my data

 <div id="invoiceBodyWrapper">
<div data-bind="template: { name: 'invoice-template', foreach: surveys }">
</div>

<div class="invoiceWrapper">
    </div>
    <div id="completePictureWrapper" data-bind="template: { name: 'photo-template',     foreach: new Array(Attributes) }"></div>

</div>
</script>

<script type="text/html" id="photo-template">
<!-- ko if: classification === 'photo' -->
<div id="pictureWrappers">
    <img class="img" data-bind="attr: { src: 'http://myimagepath/download/full/' +    $index()+1 }" />
</div>
<!-- /ko -->
 </script>
  <script src="~/Scripts/DamageInvoiceCreation.js"></script>

I need a way to limit my attributes foreach loop to only show 2 of the 3 attributes. I have only found a few things on how to do this and they seem very overly complicated. I cannot imagine there is not a simple way to do this in knockout.

like image 314
EntryLevel Avatar asked Jul 24 '13 17:07

EntryLevel


2 Answers

JavaScript arrays include the excellent slice method that should fill your need nicely:

template: { name: 'photo-template', foreach: Attributes.slice(0,2) }

But as @Patrick-M mentioned, you don't need a loop:

template: { name: 'photo-template', data: Attributes[0] }
template: { name: 'photo-template', data: Attributes[1] }

My Repeat binding includes an option for limiting the number of repetitions:

<div data-bind="repeat: { foreach: Attributes, count: 2 }"
      data-repeat-bind="template: { name: 'photo-template', data: $item() }">
</div>
like image 139
Michael Best Avatar answered Oct 14 '22 14:10

Michael Best


If you always have 3 attributes and you always only want to show 2 of them, you don't need to foreach them, exactly.

However, there is the special binding context variable $index(), which will let you do some basic hiding, although it wouldn't prevent rendering. Because $index is 0-based, the condition is $index() < 2. As Andrey points out in the comments, $index is an observable, so you have to call it with parentheses as a method, or comparisons won't do what you expect (you'll be comparing an int against a function).

<ul data-bind="foreach: survey.Attributes">
    <li data-bind="visible: $index() < 2">
        Name: <span data-bind="text: Name"> </span><br/>
        Type: <span data-bind="text: Type"> </span><br/>
        Year: <span data-bind="text: Year"> </span><br/>
    </li>
</ul>

If you want a generic limiter on a foreach loop, you're right, it's not simple. You would have to make a custom binding.

Another approach you could consider is pre-processing your data in the viewmodel. When you set this.survey = data;, you could remove any of the attributes you don't want to display at that point.

Edit: I see from your edit that you know about the ko: if psuedo-elements. I completely forgot about those, but you could easily use one to prevent rendering template items beyond a certain index. The foreach would still evaluate the observable, that should not have any kind of huge overhead by itself.

like image 40
Patrick M Avatar answered Oct 14 '22 12:10

Patrick M