Placing a container around each "n" element in a JS foreach Loop knockout

I would like to build the following HTML using the JS foreach loop knockout ...

<div>
  <div>
    <article></article>
    <article></article>
    <article></article>
  </div>
  <div>
    <article></article>
    <article></article>
    <article></article>
  </div>
</div>

      

... where each article is an element in an array.

I've tried the following, which seems logically healthy, but it doesn't work - I'm guessing the knockout is getting confused with unbalanced tags inside comments ...

<div data-bind="foreach: articles()">
  <!-- ko: if ($index() % 3 == 0)
    <div>
  <!-- /ko -->

    <article></article>

  <!-- ko: if ($index() % 3 == 2)
    </div>
  <!-- /ko -->
</div>

      

Any thoughts on how I can achieve this would be appreciated!

+3


source to share


2 answers


My attitude to this when solving problems like this has always been that the view model should be structured as close to the view as possible so that you don't do the logic in the view itself. So the place to group your article array is used ko.computed

in the viewmodel to build a structure like:

groupedArticles = [
    [article1, article2, article3],
    [article4, article5, article6]
]

      

then in your opinion you can do:

<!-- ko foreach: groupedArticles -->
<div>
    <!-- ko foreach: $data -->
    <article></article>
    <!-- /ko -->
</div>
<!-- /ko -->

      

Let me know if this makes sense or not; if not i can try to add a script for the demo.



Update

I found a fiddle that used this pattern. I needed to update KO to the latest version to get it working, now you can try: http://jsfiddle.net/hFPgT/160/

This is the question How to get knocked out for grouping foreach

And the relevant code:

this.grouped = ko.computed(function () {
        var rows = [], current = [];
        rows.push(current);
        for (var i = 0; i < this.items.length; i += 1) {
            current.push(this.items[i]);
            if (((i + 1) % 4) === 0) {
                current = [];
                rows.push(current);
            }
        }
        return rows;
}, this);

      

+1


source


Like @sifriday did, I would go for a separate computed array that I would call articleGroups

(or view). In my later understanding of Knockout, I found it most convenient to put all view-related logic (here: grouping) inside the component's viewModels, so I built a component for it to do this here. An added benefit is that you can pass parameters in the view; try the below snippet with different values ​​for "groupBy" for example.



// setup
var articles = [];
for (var i = 0; i < 50; i++)
  articles.push({i: i+1, text: "text"});

// listview component
ko.components.register('article-view', {
  viewModel: function(params) {
    var groupBy = this.groupBy = ko.observable(params.groupBy);
    this.articleGroups = ko.computed(function() {
      var result = [], group = groupBy();
      ko.utils.arrayForEach(ko.unwrap(params.data), function(item, index) {
        if (index % group === 0) 
          result.push([item]);
        else 
          result[result.length-1].push(item);
      });
      return result;
    });
  },
  template: {element: 'article-group'}
});

// viewModel instantiation
VM = { articles: ko.observableArray(articles)};
ko.applyBindings(VM);
      

body>div>div>div { border-bottom: 1px solid gray; padding-bottom: 10px; }
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script type="text/template" id="article-group">
  <input type="number" data-bind="value: groupBy" min="1" max="50">
  <div data-bind="foreach: articleGroups, as: 'group'">
    <div data-bind="foreach: $data, event: {load: console.log($data)}">
      <article data-bind="text: i"></article>
    </div>
  </div>
</script>
<div data-bind="component: {name: 'article-view', params: {groupBy: 5, data: articles}}"></div>
      

Run codeHide result


+1


source







All Articles