Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use knockout to iterate over an object (not array)

I want to use something similar to the Knockout foreach construct to iterate over the properties of an object. Here is what I am trying to create...

DESIRED RESULT

<table>     <tr>         <td>Name 1</td>         <td>8/5/2012</td>     </tr>     <tr>         <td>Name 2</td>         <td>2/8/2013</td>     </tr> </table> 

However, my model looks like this...

JS

function DataModel(){     this.data = ko.observableArray([{                         entityId: 1,                         props: {                             name: 'Name 1',                             lastLogin: '8/5/2012'                         }                     },                     {                         entityId: 2,                         props: {                             name: 'Name 2',                             lastLogin: '2/8/2013'                         }                     }]); }  var dataModel = new DataModel(); ko.applyBindings(dataModel); 

Each row has an entityId and props which is an object itself. This template doesn't work, but how would I change it to generate the desired table above?

EDIT: The props in this example are name and lastLogin, but I need a solution that is agnostic to what is contained inside props.

I have this FIDDLE going as well.

HTML

<div data-bind="template: { name: 'template', data: $data }"></div>  <script type="text/html" id="template">     <table>         <tr data-bind="foreach: data()">             <td data-bind="text: entityId"></td>           </tr>     </table>  </script> 
like image 336
John Livermore Avatar asked Feb 12 '13 17:02

John Livermore


People also ask

Can we iterate an object?

There are two methods to iterate over an object which are discussed below: Method 1: Using for…in loop: The properties of the object can be iterated over using a for..in loop. This loop is used to iterate over all non-Symbol iterable properties of an object.

For what purpose do we use forEach binding in Ko?

Purpose. The foreach binding duplicates a section of markup for each entry in an array, and binds each copy of that markup to the corresponding array item. This is especially useful for rendering lists or tables.

Which method is used to iterate over arrays and objects?

Lodash forEach() to Iterate over Objects Lodash has a helpful iteration methods, such as forEach and map that work on objects as well as arrays. console.


2 Answers

In a modern browser (or with an appropriate polyfill) you can iterate over Object.keys(obj) (the method returns only own enumerable properties, meaning that there is no need for an additional hasOwnProperty check):

<table>   <tbody data-bind="foreach: {data: data, as: '_data'}">     <tr data-bind="foreach: {data: Object.keys(props), as: '_propkey'}">       <th data-bind="text: _propkey"></th>       <td data-bind="text: _data.props[_propkey]"></td>     </tr>   </tbody> </table> 

Fiddled.

NB: I was simply curious to see if this would work, the template body above is more polluted than what I'd like to use in production (or come back to a few months later and be like "wtf").

Custom binding would be a better option, my personal preference though would be to use a computed observable or a writeable computed observable (the latter would be handy when working with json responses a-la restful api).

like image 52
Oleg Avatar answered Sep 18 '22 21:09

Oleg


You could always create a binding handler to handle the transformation.

ko.bindingHandlers.foreachprop = {   transformObject: function (obj) {     var properties = [];     ko.utils.objectForEach(obj, function (key, value) {       properties.push({ key: key, value: value });     });     return properties;   },   init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {     var properties = ko.pureComputed(function () {       var obj = ko.utils.unwrapObservable(valueAccessor());       return ko.bindingHandlers.foreachprop.transformObject(obj);     });     ko.applyBindingsToNode(element, { foreach: properties }, bindingContext);     return { controlsDescendantBindings: true };   } }; 

Then apply it:

<div data-bind="template: { name: 'template', data: $data }"></div>  <script type="text/html" id="template">     <table>         <tbody data-bind="foreach: data">             <tr data-bind="foreachprop: props">                 <td data-bind="text: value"></td>             </tr>         </tbody>     </table>  </script> 
like image 40
Jeff Mercado Avatar answered Sep 20 '22 21:09

Jeff Mercado