Using Knockout Mapping for Complex JSON

Most knockouts seem very intuitive. One thing that is strange to me is how the mapping plugin works. I was expecting / hoping that I could feed it JSON with an ajax call and have some sort of "dynamic" view model available for reference in my HTML.

The description of the mapping plugin description even makes it sound like this: how it works:

"If your data structures get more complex (for example, they contain children or contain arrays), it becomes very cumbersome to handle manually. What the mapping plugin allows is to create a mapping from a regular JavaScript object (or JSON structure) to an observable model. review ".

But it looks like you really need to define the view model in your code first and then you can populate it after the fact using a mapping plugin and some JSON data. Is it correct?

A concrete example of what I was trying to do.

I am trying to use Knockout with Solr (a search engine that returns JSON search results). The JSON data skeleton structure returned by Solr is as follows:

  {
      "responseHeader": {
          "status": 0,
          "QTime": 0,
          "params": {
              "facet": "true",
              "facet.field": "System",
              "q": "testphrase",
              "rows": "1",
              "version": "2.2"
          }
      },
      "response": {
          "numFound": 0,
          "start": 0,
          "maxScore": 0.0,
          "docs": []
      },
      "facet_counts": {
          "facet_queries": {},
          "facet_fields": {
              "System": []
          },
          "facet_dates": {},
          "facet_ranges": {}
      },
      "highlighting": {}
  }

      

In fact, that's the structure I load into my renderer display when I first installed it.

It's just that you have a little understanding of how JSON data is returned from Solr: the response.docs array contains an array of hashes, where the hash data consists of the key / values ​​for your indexed document data. Each hash in the array is one document returned in the search results.

This part seems to be very well visible.

The "underline" part of the JSON is causing me problems. When I try to reference highlight fields in my HTML, I get ReferenceErrors. Here's an example of what a highlight field might look like in JSON:

"highlighting": {
    "2-33-200": {
        "Title": ["1992 <b>Toyota</b> Camry 2.2L CV Boots"]
    },
    "2-28-340": {
        "Title": ["2003 <b>Toyota</b> Matrix 2.0L Alignment"]
    },
    "2-31-2042": {
        "Title": ["1988 <b>Toyota</b> Pickup 2.4L Engine"]
    }
}

      

I have a foreach in my HTML that tries to parse each response.docs element, and if the highlighted portion of the object contains a match for that Document Id field, I want to replace the highlighted title, not the default title. (In the code below, "Results" is the name of the view model. I am mapping JSON for.)

<div id="search-results" data-bind="foreach: Results.response.docs">
    <div data-bind="attr: { id: 'sr-' + Id }" class="search-result">
        <h3 class="title"><a data-bind="html: (($root.Results.highlighting[Id]['Title'] != undefined) ? $root.Results.highlighting[Id]['Title'] : Title), attr: {href: Link}"></a></h3>
        <span class="date" data-bind="text: DateCreated"></span>
        <span class="snippet" data-bind="html: Snippet"></span>
    </div>
</div>

      

When I try to use this, I always get this error:

Uncaught Error: Unable to parse bindings.
Message: TypeError: Cannot read property 'Title' of undefined;
Bindings value: html: (($root.Results.highlighting[Id]['Title'] != undefined)  ? $root.Results.highlighting[Id]['Title'] : Title), attr: {href: Link}

      

I have tried variations on how I am referencing the data, but I just cannot access it.

Edit I'm making progress a bit. In my display definition, I now specify "highlight" as follows:

"highlighting": ko.observable({})

      

Instead of pointing the selection to {}. Now I can at least peek into the selection data when I do my mapping. But I still see strange errors.

I simplified my test HTML to just spit out the highlighting data for each search result:

<div id="search-results" data-bind="foreach: Results.response.docs">
    <pre data-bind="text: JSON.stringify(ko.toJS($root.Results.highlighting()[Id()]), null, 2)"></pre>
</div>

      

Now, multiple tags <pre>

are returned that look like this:

{
  "Title": [
    "1992 <b>Toyota</b> Camry 2.2L CV Boots"
  ]
}

      

However, if I change this HTML code to this:

<pre data-bind="text: $root.Results.highlighting()[Id()]['Title']"></pre>

      

I keep getting errors like this:

Message: TypeError: Cannot read property 'Title' of undefined;
Bindings value: text: $root.Results.highlighting()[Id()]['Title']

      

Doesn't make sense to me! My previous test shows that the available data contains the "Title" key, why can't I access this data?

Edit I created a jsfiddle , but of course ... it works as expected. I cannot reproduce my problem on jsfiddle .: - (

Edit OK I am doing some steps here, but I am still very confused as to what is going on. I first changed my debug HTML to this:

<div id="search-results" data-bind="foreach: Results.response.docs">
    <pre data-bind="text: console.log($root.Results.highlighting()[Id()])"></pre>
</div>

      

Then I sent my ajax call and I noticed this output in the Chrome console:

undefined
undefined
> Object

      

So for some reason the loop is foreach

looping through the 3 Results.response.docs and the first two don't match anything in my highlightlights () object, so they return undefined - which is why my attempt to pull the .Title property failed.

To confirm this, I wrapped ko if: $root.Results.highlighting()[Id()]

around this block and was finally able to access the .Title property during the foreach loop without a JS error.

This still leaves me wondering why / how there are 3 Results.response.docs objects being looped. Perhaps the foreach binding is fired 3 times, and the first 2 times the highlight object is empty, and the third time it gets filled? But it's hard for me to understand why that would be.

Another possible hint: if I run the ajax call a second time without reloading the page, I see that these 3 "passes" all return a valid object each time in the console log. So instead of two undefined

and an object, it's three objects, all on a line.

In my HTML output, however, I only see one line of data. So it looks like it doesn't loop over 3 elements, but actually runs 3 times. The question remains ... WHY?

+3


source to share


1 answer


The display plugin works as you expect. Your problem is that you are expecting the plugin to create observables at every object level. This is not how the plugin works. This will only create observable "leaf" properties. So in your case $root.Results.highlighting

it is not instantiated as an observable. However, id properties in documents are created as observables, so there is a solution.

$root.Results.highlighting[Id()]

      

I believe you may have been confused because one of your scripts was assigning itself. Results twice that made it work in the same direction when in fact the problem was masked.



Here is a working version

http://jsfiddle.net/madcapnmckay/UaBKe/

Hope it helps.

+3


source







All Articles