简体   繁体   中英

Combining / Structuring multiple knockout observable arrays

I have 2 observable arrays in my view model:

self.main = ko.observablearray();
self.sub = ko.observablearry();

Main is loaded with a getjson call and returns 12 items with a description / sort order (ie 1-12) into the self.main array and the sub returns child descriptions to the main.

The json for main return:

<storedProc_Result>
<ID>4</ID>
<mainDescription>
      foo foo foo
</mainDescription>
<SortOrder>1</SortOrder>
</storedProc_Result>

The json for sub return:

<storedProc2_Result>
<SortOrder>1.1</SortOrder>
<ID>21</ID>
<SubDescription>bar bar bar</SubDescription>
</storedProc2_Result>
<storedProc2_Result>
<SortOrder>1.2</SortOrder>
<ID>23</ID>
<SubDescription>bar bar bar</SubDescription>
</storedProc2_Result>

How will I loop through and combine these 2 arrays based off of the SortOrder value so I can do a foreach loop on my View page? IE

  • Main Description 1
    • Sub Description 1.1
    • Sub Description 1.2
  • Main Description 2
    • Sub Description 2.1
  • ...

1. The Data

Let's first explore the data you've provided in a more readable format:

Main

{
  ID: 4,
  mainDescription: "foo foo foo",
  SortOrder: "1"
}

Sub

{
  ID: 21,
  SubDescription: "bar bar bar",
  SortOrder: "1.1"
}

2. The viewmodels

From your desired output, we learn that we need:

An AppViewModel

{
  mains: [ /* MainViewModel */ ]
}

A MainViewModel

{ 
  title: "foo foo foo",
  numberLabel: "1",
  subs: [ /* SubViewModel */ ],

  // The label as a number
  sortOrder: parseInt("1", 10)
}

A SubViewModel

{
  title: "bar bar bar",
  numberLabel: "1.1",

  // We sort by the second number in the label
  sortOrder: parseInt("1.1".split(".")[1], 10),

  // We group by the first part of the label
  groupKey: "1.1".split(".")[0]
}

3. The views

With the structure of viewmodels as proposed, we can create the view:

<ul data-bind="foreach: mains">
  <li>
    <p data-bind="text: title + ' ' + numberLabel">
    <ul data-bind="foreach: subs">
      <li data-bind="text: title + ' ' + numberLabel"></li>
    </ul>
  </li>
</ul>

4. Sorting viewmodel instances

Because we have numeric sortKey props, we can do a numeric compare to sort:

// Sort utils
const numericSort = (x, y) => x > y ? 1 : x < y ? -1 : 0;
const vmSort = (vm1, vm2) => numericSort(vm1.sortKey, vm2.sortKey);

5. Grouping viewmodels

We can group a list of SubViewModel instances by their groupKey :

const groupSubs = subs => subs.reduce(
  (gs, s) => Object.assign(
    gs,
    { [s.groupKey]: (gs[s.groupkey] || []).concat(s) }
  ), { }
)

Now, we can create a nice chain from data to app using ko.pureComputed instances.

const subData = ko.observableArray([]);
const subVMs = ko.pureComputed(
  () => subData().map(SubViewModel)
);
const subGroups = ko.pureComputed(
  () => groupSubs(subVMs())
);

const mainData = ko.observableArray([]);
const mainVMs = ko.pureComputed(
  () => mainData().map(m => MainViewModel(m, subGroups))
);

ko.applyBindings(AppViewModel(mainVMs));

Working example

This flow illustrated in a working example:

 const mains = ko.observableArray([]); const subs = ko.observableArray([]); const subGroup = sub => sub.sortOrder.split(".")[0] const subsByMain = ko.pureComputed(() => subs() .map(sub => [subGroup(sub), sub]) .reduce( (groups, [id, sub]) => Object.assign( groups, { [id]: (groups[id] || []).concat(sub) } ), {} ) ); const Main = function(config, groups) { this.title = config.description; this.sortOrder = config.sortOrder; this.subs = ko.pureComputed(() => groups()[config.sortOrder] || [] ); }; ko.applyBindings({ mains: ko.pureComputed(() => mains().map(cfg => new Main(cfg, subsByMain)) ) }); const getMainData = cb => setTimeout(() => cb([ { id: 1, description: "foo foo foo", sortOrder: "1" }, { id: 2, description: "bar bar bar", sortOrder: "2" } ]), 500); const getSubData = cb => setTimeout(() => cb([ { id: 3, description: "foo 1", sortOrder: "1.1" }, { id: 4, description: "foo 2", sortOrder: "1.2" }, { id: 5, description: "bar 1", sortOrder: "2.1" }, { id: 6, description: "bar 2", sortOrder: "2.3" } ]), 300); getMainData(mains); getSubData(subs);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script> <ul data-bind="foreach: mains"> <li> <p data-bind="text: title + ' ' + sortOrder"> <ul data-bind="foreach: subs"> <li data-bind="text: description + ' ' + sortOrder"></li> </ul> </li> </ul>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM