0

I really am struggling with something that I thought was simple...

I am making a simple search-result table based on $.getJSON call, and want to keep my code as "generic" as possible.

In my (simplified) HTML :

<form id="searchForm">
(...)
    <button type="button" onclick="search()">Search</button>
</form>

(...)
<tbody data-bind="foreach: data">
    <tr>
        <td data-bind="text: FOO"></td>
(...)
        <td data-bind="text: BAR"></td>
    </tr>
</tbody>

Then in my javascript (in script tags lower in the page):

var search = function(){
    var form = $('#searchForm');
    $.getJSON("php/query/jsonQuery.php?jsonQuery=search", form.serialize(), function(jsonAnswer, textStatus) {
        console.log(jsonAnswer);
        if(typeof viewModel === 'undefined'){
            var viewModel = ko.mapping.fromJS(jsonAnswer);
            ko.applyBindings(viewModel);
        }
        else{
            ko.mapping.fromJS(jsonAnswer, viewModel);
        }
        $('#divResults').show();
//         console.log(viewModel)
    });
}

This works fine on the first "search" click... but not the following : Error You cannot apply bindings multiple times to the same element.

As you can guess, this very ugly "if" testing viewModel is a desperate attempt to get rid of that error.

I've tried many things but I just can't figure out how to do it properly...

I've read this Knockout JS update view from json model and this KnockoutJs v2.3.0 : Error You cannot apply bindings multiple times to the same element but it didn't help me much... maybe because the search() function isn't called on load (and indeed shouldn't be).

Any KO master to give me a clue? Thanks in advance for your help!

Vigon
  • 43
  • 1
  • 7

1 Answers1

1

This is how I would be approaching what you are trying to accomplish.

var searchService = {
  search: function(form, vmData) {
    //$.getJSON("php/query/jsonQuery.php?jsonQuery=search", form.serialize(), function(jsonAnswer, textStatus) {
      var jsonAnswer = [{
        FOO: "Item 1 Foo",
        BAR: "Item 1 Bar"
      }, {
        FOO: "Item 2 Foo",
        BAR: "Item 2 Bar"
      }]
      ko.mapping.fromJS(jsonAnswer, [], vmData);
      //})
    }
};

var PageViewModel = function() {
  var self = this;
  self.data = ko.observableArray();
  self.hasResults = ko.pureComputed(function() {
    return self.data().length > 0;
  });

  self.search = function() {
    var form = $('#searchForm');
    searchService.search(form, self.data);
  };
};

ko.applyBindings(new PageViewModel());
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>

<form id="searchForm">
  <button type="button" data-bind="click:search">Search</button>
</form>
<div data-bind="visible: !hasResults()"><b>No Results</b></div>
<div data-bind="visible: hasResults">
  <table class="table">
    <thead>
      <tr>
        <td>FOO</td>
        <td>BAR</td>
      </tr>
    </thead>
    <tbody data-bind="foreach: data">
      <tr>
        <td data-bind="text: FOO"></td>
        <td data-bind="text: BAR"></td>
      </tr>
    </tbody>
  </table>
</div>
<br/>
<pre><code data-bind="text: ko.toJSON($root)"></code></pre>
Nathan Fisher
  • 7,961
  • 3
  • 47
  • 68
  • 1
    THAT was incredibly helpfull and clear! I had to adapt some tweaks, but I couldn't imagine a better answer. Thanks a LOT! – Vigon May 06 '20 at 12:11