1

For simplicity's sake, I have a simple array. What I'm looking to do is to loop through the array's questions for the first category, and then loop through the array's questions for the second category, and so on.

The "Answer" will be text input fields or radio buttons or whatever.

How would I "nest" the two loops in knockout.js? I can't separate it into two arrays, because then I lose the relationship between the two loops.

//knockout.js viewModel
function SurveyViewModel() {
    var self = this;
    self.EvaluationElement = ko.observableArray([
        {category: "Category1", question: "Question #1"},
        {category: "Category1", question: "Question #2"},
        {category: "Category2", question: "Question #3"},
        {category: "Category2", question: "Question #4"},
        {category: "Category3", question: "Question #5"},
        {category: "Category3", question: "Question #6"},
        {category: "Category4", question: "Question #7"},

And I want to build a view (HTML) that is essentially a table nested as follows:

<table>
  <tr>
    <th colspan="3">Category 1</th>
  </tr>
  <tr>
    <td>Question #1</td>
    <td>Answer</td>
    <td></td>
  </tr>
  <tr>
    <td>Question #2</td>
    <td>Answer</td>
    <td></td>
  </tr>
  <tr>
    <td colspan="3">Category 2</td>
  </tr>
  <tr>
    <td>Question #3</td>
    <td>Answer</td>
    <td></td>
  </tr>
  <tr>
    <td>Question #4</td>
    <td>Answer</td>
    <td></td>
  </tr>
  <tr>
    <td>Question #5</td>
    <td>Answer</td>
    <td></td>
  </tr>
</table>

I'm trying to do something like this... there are obvious errors, but hopefully it conveys my thinking.

<br>
<table class="table table-hover">
   <tbody data-bind="foreach: EvaluationElement/Category">
      <tr>
        <td colspan="2" data-bind="text: category></td>
      </tr>
      <data-bind="foreach: EvaluationElement">
           <tr>
            <td data-bind="text: question"></td>
            <td>Answer</td>
            <td></td>
          </tr>
       <// close loop>
   </tbody>
</table>
<br>
<button data-bind="click: submitSurvey">Submit</button>

1 Answer 1

1

You should restructure your data in your view model to keep your view nice and simple. If your view requires table bodies per category, that's what the viewmodel data should reflect.

To restructure the list of questions in to a list of categories, you can use Knockout's pureComputed.

We create a function that does the data transformation from [ { category, question } ] to [ { category, questions: [ { category, question ] } ].

This very much resembles a groupBy. There are many ways to write a grouping function; search for "group by property in javascript" if you want to learn more about the different approaches.

With the new structure, the view very much resembles your desired format:

function SurveyViewModel() {
  var self = this;
  self.EvaluationElement = ko.observableArray([{
      category: "Category1",
      question: "Question #1"
    },
    {
      category: "Category1",
      question: "Question #2"
    },
    {
      category: "Category2",
      question: "Question #3"
    },
    {
      category: "Category2",
      question: "Question #4"
    },
    {
      category: "Category3",
      question: "Question #5"
    },
    {
      category: "Category3",
      question: "Question #6"
    },
    {
      category: "Category4",
      question: "Question #7"
    }
  ]);

  self.categories = ko.pureComputed(function() {
    // Assumes questions sorted by category
    return self.EvaluationElement().reduce(
      function(cats, q, i, qs) {
        const prev = qs[i - 1];
        
        if (!prev || prev.category !== q.category) {
          cats.push({ 
            category: q.category,
            questions: [ q ]
          });
        } else {
          cats[cats.length - 1].questions.push(q);
        };
        
        return cats;
      }, []);
  });
};

ko.applyBindings(new SurveyViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
  <tbody data-bind="foreach: categories">
    <tr>
      <td colspan="2" data-bind="text: category"></td>
    </tr>
    <!-- ko foreach: questions -->
    <tr>
      <td data-bind="text: question"></td>
      <td>Answer</td>
      <td></td>
    </tr>
    <!-- /ko -->
  </tbody>
</table>

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, that is very helpful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.