2016-06-29 4 views
2

Есть ли способ вручную увеличить свойство ViewModel из нокаут foreach?Увеличение счетчика в вложенном цикле foreach

Я хочу сделать что-то логически эквивалентно:

var inc = 0; 
for (var i = 0; i < 3; i++) 
{ 
    for (var j = 0; j < 3; j++) 
    { 
     inc++; 
     alert(inc + ': ' + i + ',' + j); // but write this to the dom 
    } 
} 

Я попытался использовать $index, но это только относится к токовой петле, а не внутренней или родительского цикла.

Массивы могут быть разных размеров, поэтому я не могу просто вычислить $parentContext.$index * [count of child items] + current $index.

То, что я пытаюсь достичь:

<div data-bind="foreach: Categories"> 
    <div data-bind="foreach: SubCategories" > 
     <div data-bind="text: [someIncrementer] +': ' + $parent.Category + ',' + $data></div> 
     <!-- should output: 
     1: A,1 
     2: A,2 
     3: A,3 
     4: B,1 
     5: B,3 
     --> 
    </div> 
</div> 
var ViewModel = function() 
{ 
    var self = this; 
    self.Categories = ko.observableArray([ 
     {Category: 'A', SubCategories: [1,2,3]}, 
     {Category: 'B', SubCategories: [1,3]} 
    ]); 
} 

ответ

2

Нет необходимости делать это любой более сложной, чем она должна быть. Вы могли бы попробовать что-то вроде этого:

var ViewModel = function() { 
 
    var self = this; 
 
    var index = 0; 
 
    self.inc = function(val) { 
 
    return ++index; 
 
    } 
 

 
    self.Categories = ko.observableArray([{ 
 
    Category: 'A', 
 
    SubCategories: [1, 2, 3] 
 
    }, { 
 
    Category: 'B', 
 
    SubCategories: [1, 3] 
 
    }]); 
 
}; 
 

 
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> 
 
<ul data-bind="foreach: Categories"> 
 
    <!-- ko foreach: SubCategories --> 
 
    <li> 
 
    <span data-bind="text: $root.inc() + ': ' + $parent.Category + ', ' + $data"></span> 
 
    </li> 
 
    <!-- /ko --> 
 
</ul>

+0

Это прекрасно работает (для моих нужд). Я могу создать другой метод, чтобы получить текущий инкремент/просто выставить внутренний инкрементер на объект, 'self.Incrementer = 0' – AndrewP

+1

Это не сработает, я думаю, не в реальных сценариях, где есть наблюдаемые в игре. Каждый раз, когда привязка «text» переоценивается (например, всякий раз, когда имя категории будет меняться, если оно будет наблюдаемым), функция «inc» будет вызываться снова, без возврата нумерации обратно в 0. – Jeroen

+0

Правильно, вы бы должны учитывать изменения в вашем массиве. –

2

Создать вычисленные наблюдаемый с уплощенным массивом для этого:

var ViewModel = function() { 
 
    var self = this; 
 

 
    self.Categories = ko.observableArray([ 
 
    {Category: 'A', SubCategories: [1,2,3]}, 
 
    {Category: 'B', SubCategories: [1,3]} 
 
    ]); 
 

 
    self.FlattenedSubcategories = ko.computed(function() { 
 
    var result = [], cats = self.Categories(), index = 1; 
 
    for (var i = 0; i < cats.length; i++) { 
 
     for (var j = 0; j < cats[i].SubCategories.length; j++) { 
 
     result.push({ 
 
      Idx: index++, 
 
      Category: cats[i].Category, 
 
      Subcat: cats[i].SubCategories[j] 
 
     }); 
 
     } 
 
    } 
 
    return result; 
 
    }); 
 
}; 
 

 
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script> 
 

 
<div data-bind="foreach: FlattenedSubcategories"> 
 
    <div> 
 
    <span data-bind="text: Idx"></span>: 
 
    <span data-bind="text: Category"></span>, 
 
    <span data-bind="text: Subcat"></span> 
 
    </div> 
 
</div>

+0

Это работает тоже. Иногда полезно вставлять 'foreach', чтобы иметь возможность создать определенную структуру HTML, в этом примере это не имеет никакого значения. – Tomalak

+0

Jeroen, этот подход может работать. Моя фактическая модель намного сложнее (пыталась заглушить ее для простого примера StackOverflow). Я создаю более сложную структуру HTML, поэтому упорядоченный список не будет работать. Вычисленный сплющенный массив мог бы работать, но я должен был бы сделать поиск против него. Думаю, мне может понадобиться вычислить серверную сторону «индекс» как один из элементов данных. Спасибо, хотя это было полезно! – AndrewP

+0

Я понял, что у вас будут более сложные объекты (суб) категории. Мое решение наверняка сохранится в определенной степени, в зависимости от * фактических * данных и моделей просмотра. - Я не уверен, хотя то, что вы подразумеваете под «*, нужно сделать с ним», но вам может потребоваться задать отдельный вопрос с воспроизведением по этой теме. – Jeroen

1

Вы могли бы использовать a <ol>/<li> с цифровым счетчиком или CSS counter.

Это работает, если номер предназначен исключительно для показа, и от этого ничего не зависит.

var ViewModel = function() 
 
{ 
 
    var self = this; 
 
    self.Categories = ko.observableArray([ 
 
     {Category: 'A', SubCategories: [1,2,3]}, 
 
     {Category: 'B', SubCategories: [1,3]}, 
 
     {Category: 'C', SubCategories: [7,8,9]}, 
 
     {Category: 'D', SubCategories: [10,11,12,13]} 
 
    ]); 
 
} 
 

 
ko.applyBindings(new ViewModel());
.categories { 
 
    counter-reset: category; 
 
} 
 
.subcategory::before { 
 
    counter-increment: category; 
 
    content: counter(category) ": "; 
 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> 
 

 
<div class="categories" data-bind="foreach: Categories"> 
 
    <div data-bind="foreach: SubCategories"> 
 
     <div> 
 
      <span class="subcategory" data-bind="text: $parent.Category + ',' + $data"></span> 
 
     </div> 
 
    </div> 
 
</div>

0

Другого варианта:

Как вы сказали, нокаут связывающего контекст выставляет $index наблюдаемые, который содержит индекс в цикле. Также есть $parentContext, чтобы ссылаться на контекст, который выше в цепочке.

Для простого, 2 уровня вложенного цикла, можно создать вспомогательный метод, который использует текущий $index и материнской компании $index вычислить общий индекс:

// cIndex = current category's index 
// sIndex = current subcategory's index 
self.getSubIndex = function(cIndex, sIndex) { 
    return sIndex + self.Categories 
    .slice(0, cIndex) 
    .reduce(function(count, c) { 
     return count + c.SubCategories.length; 
    }, 0); 
} 

Это будет немного раздражает писать хотя:

<span data-bind="text: $root.getSubIndex($parentContext.$index(), $index())"> </span> 

Вы также могли бы пройти $context и получить индексы внутри метода getSubIndex. Вы можете даже пережить контекстную цепочку рекурсивно, пока не будет найдено больше $index.

Лично я предпочел бы создать index, рассчитанный в моделях SubCategory, которые будут чувствовать себя немного менее взломанными. Однако для этого требуется, чтобы SubCategory имел доступ к его родительскому объекту ...

var ViewModel = function() { 
 
    var self = this; 
 
    
 

 
    self.Categories = ko.observableArray([{ 
 
    Category: 'A', 
 
    SubCategories: [1, 2, 3] 
 
    }, { 
 
    Category: 'B', 
 
    SubCategories: [1, 3] 
 
    }]); 
 
    
 
    self.getSubIndex = function(cIndex, sIndex) { 
 
    return sIndex + self.Categories 
 
     .slice(0, cIndex) 
 
     .reduce(function(count, c) { 
 
     return count + c.SubCategories.length; 
 
     }, 0); 
 
    } 
 
}; 
 

 
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script> 
 
<ul data-bind="foreach: Categories"> 
 
    <!-- ko foreach: SubCategories --> 
 
    <li> 
 
    <span data-bind="text: $root.getSubIndex($parentContext.$index(), $index()) + ': ' + $parent.Category + ', ' + $data"></span> 
 
    </li> 
 
    <!-- /ko --> 
 
</ul>

Смежные вопросы