Paba
Paba

Reputation: 1115

Ajax queries with Knockout JS

I try to display a list of items in a table using Knockout JS. This is like a typical search interface, where the user types a query and the interface displays the list of results returned by the search engine. I first retrieve them from the server through an AJAX call and then try to bind them. This works for the first query very well. But when I try the second query (and subsequent queries), each result item is represented 10 times (duplicated), then for the 3rd query each item is displayed for like 30 times (and so on). Given below is the code.

<body >
<p>
  <input type="search" id="skynet-query" name="q" placeholder="scientific search" autofocus />
  <input type="submit" id="skynet-submit" value="Ignite!" onclick="getAttachments(document.getElementById('skynet-query').value)" />
</p>
<article data-bind="foreach: seats">   
        <h3>
            <input data-bind="attr:{value: papers().id }" type="checkbox" name="article-to-basket" />
            <a data-bind="attr:{href: papers().url }, text: papers().title"> </a>
        </h3>
        <address class="authors" data-bind="text: papers().authors"/>
</article>
</body>

Following is the Script part

<script>

 var array = new Array();

function getAttachments(keyword) {

  var request = $.ajax({
    type: "GET",
    datatype: "json",
    url: "get-papers?q="+keyword+"&format=json&full-articles=true&kw-count=10&article-count=10&task-type=m1"
  });

  request.done(function (response) {

    for (i=0;i<response.articles.length; i++){
        array[i] = new PaperData(response.articles[i]);
    }
    ko.cleanNode(document);
    ko.applyBindings(new ReservationsViewModel());
    console.log("DONE");
  });

}

function PaperData(papers) {
    var self = this;
    self.papers = ko.observable(papers);
}

function ReservationsViewModel() {
    //var self = this;
    self.seats = ko.observableArray(array);

}

</script>

Can someone help me to find what's wrong with this code?

This is what my data-model looks like

 {
    articles: [
    {
    "is-saved": false,
    title: "title",
    abstract: "Abstract",
    date: "2005-01-01 00:00:00",
    "publication-forum": "forum",
    "publication-forum-type": "article",
    authors: "Authors",
    keywords: "keyword1, keyword2, keywordn",
    id: "4f5a318e573ce53e03000015"
    }
    ]

    }

Upvotes: 0

Views: 1500

Answers (1)

Tomalak
Tomalak

Reputation: 338386

Your approach to knockout is not quite right.

  • Every value you display on the screen is supposed to be an observable that is tied into your view model.
  • For example, input elements with names are unnecessary - the value binding takes care of updating the view model. You never have to address an <input> by name.
  • Don't ever use inline event handlers like onclick. Not in Knockout and not anywhere else.
  • Don't use global variables. Your problem comes from the use of a global array variable that in reality should be an observable array on your view model.
  • Don't clear out everything and reconstruct your entire view model from scratch just because something changed. Knockout will handle any partial updates.

Here's an improved approach.

<p>
  <input type="search" data-bind="value: query" placeholder="scientific search" autofocus />
  <input type="submit" data-bind="click: getAttachments" />
</p>
<article data-bind="foreach: seats">   
    <h3>
        <input data-bind="value: id, checked: selected" type="checkbox" />
        <a data-bind="attr: {href: url}, text: title"></a>
    </h3>
    <address class="authors" data-bind="text: authors"/>
</article>

and

function PaperData(data) {
    ko.utils.extend(this, data);
    this.selected = ko.observable(false);
}
PaperData.create = function (data) {
    return new PaperData(data);
};

function ReservationsViewModel() {
    var self = this;

    self.query = ko.observable();
    self.seats = ko.observableArray();

    self.queryParams = {
        "q": self.query,
        "format": "json",
        "full-articles": true,
        "kw-count": 10,
        "article-count": 10,
        "task-type": "m1"
    };

    self.getAttachments = function () {
        $.get("get-papers", ko.toJS(self.queryParams))
        .then(function (response) {
            return ko.utils.arrayMap(response.articles, PaperData.create);
        })
        .done(self.seats);
    };
}

ko.applyBindings(new ReservationsViewModel());

Notes

  • You can use .then() to transform Ajax response data on the fly.
  • You can use .done() to write the transformed result into an observable.
  • Making the queryParams a separate property of the view model allows you to dynamically tweak other query parameters just like shown with "q".
  • You can use ko.utils.extend to transfer properties from one object to another. Alternatively you could use the mapping plugin.

Upvotes: 1

Related Questions