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

前端 未结 8 542
不知归路
不知归路 2020-12-04 23:31

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

相关标签:
8条回答
  • 2020-12-05 00:10

    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).

    0 讨论(0)
  • 2020-12-05 00:19
    <table>
        <tr data-bind="foreach: {data: data, as: 'item'}">
            <td data-bind="foreach: { data: Object.keys(item), as: 'key' }">
                <b data-bind="text: item[key]"></b>
            </td>  
        </tr>
    </table>
    
    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);
    

    Hope that's helpful (pardon the brevity)

    appendix:

    Here's a working example which has been testing...

    <table class="table table-hover">
        <thead>
            <tr>
                <!-- ko foreach: gridOptions.columnDefs -->
                <th data-bind="text: displayName"></th>
                <!-- /ko -->
            </tr>
        </thead>
        <tbody>
            <!-- ko foreach: {data: gridOptions.data, as: 'item'} -->
            <tr>
                <!-- ko foreach: {data: Object.keys(item), as: 'key'} -->
                <td>
                    <span data-bind="text: item[key]"></span>
                </td>
                <!-- /ko -->
            </tr>
            <!-- /ko -->
        </tbody>
    </table>
    
    0 讨论(0)
  • 2020-12-05 00:24

    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>
    
    0 讨论(0)
  • 2020-12-05 00:24

    I am a bit late, But I think this should work, a simple solution without using any template.

    var json = [
    	{
    		"PortfolioCompanyId":240,
    		"dt":"2018-12-31 00:00:00.0",
    		"ValuationDate":"2017-09-30 00:00:00.0",
    		"capitalexpenditure":-5555660.0,
    		"workingcapitalchange":-812350.0
    	},
    	{
    		"PortfolioCompanyId":240,
    		"dt":"2019-12-31 00:00:00.0",
    		"ValuationDate":"2017-09-30 00:00:00.0",
    		"capitalexpenditure":-5613520.0,
    		"workingcapitalchange":-893530.0
    	},
    	{
    		"PortfolioCompanyId":240,
    		"dt":"2020-12-31 00:00:00.0",
    		"ValuationDate":"2017-09-30 00:00:00.0",
    		"capitalexpenditure":-5674130.0,
    		"workingcapitalchange":-982850.0
    	},
    	{
    		"PortfolioCompanyId":240,
    		"dt":"2021-12-31 00:00:00.0",
    		"ValuationDate":"2017-09-30 00:00:00.0",
    		"capitalexpenditure":-6241543.0,
    		"workingcapitalchange":-1081135.0
    	},
    	{
    		"PortfolioCompanyId":240,
    		"dt":"2022-12-31 00:00:00.0",
    		"ValuationDate":"2017-09-30 00:00:00.0",
    		"capitalexpenditure":-6865697.3,
    		"workingcapitalchange":-1189248.5
    	}
    ];
    
    var DataModel = function () {
                this.jsonArray = ko.observable(json);
            };
    ko.applyBindings(new DataModel());
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <table class="table" data-bind="foreach:jsonArray">
           <tr data-bind="foreach:Object.keys($data)"> <!-- JSON Object -->
             <td data-bind="text : $parent[$data]"></td>
           </tr>
        </table>
        
        

    0 讨论(0)
  • 2020-12-05 00:28

    (not strictly iterating over the properties, but does create the table above)

    <div data-bind="template: { name: 'template', data: $data }"></div>
    
    <script type="text/html" id="template">
        <table data-bind="foreach: data()">
            <tr>
                <td data-bind="text: props.name"></td>  
                <td data-bind="text: props.lastLogin"></td>  
            </tr>
        </table>
    </script>
    

    updated: http://jsfiddle.net/cwnEE/7/

    0 讨论(0)
  • 2020-12-05 00:35

    This is a modification of Jeff's answer, with the binding context preserved

    ko.bindingHandlers.eachProp = {
        transformObject: function (obj) {
            var properties = [];
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    properties.push({ key: key, value: obj[key] });
                }
            }
            return ko.observableArray(properties);
        },
        init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var value = ko.utils.unwrapObservable(valueAccessor()),
                properties = ko.bindingHandlers.eachProp.transformObject(value);
    
            ko.bindingHandlers['foreach'].init(element, properties, allBindingsAccessor, viewModel, bindingContext)
            return { controlsDescendantBindings: true };
        },
        update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var value = ko.utils.unwrapObservable(valueAccessor()),
                properties = ko.bindingHandlers.eachProp.transformObject(value);
    
            ko.bindingHandlers['foreach'].update(element, properties, allBindingsAccessor, viewModel, bindingContext)
            return { controlsDescendantBindings: true };
        }
    };
    

    Now apply with parent and root:

    <table>
        <tbody data-bind="foreach: data">
            <tr data-bind="eachProp: props">
                <td data-bind="text: value, click: $root.doSomething"></td>
            </tr>
        </tbody>
    </table> 
    
    0 讨论(0)
提交回复
热议问题