Подтвердить что ты не робот

Как использовать нокаут для итерации над объектом (а не массивом)

Я хочу использовать нечто похожее на конструкцию нокаута foreach, чтобы перебирать свойства объекта. Вот что я пытаюсь создать...

ЖЕЛАТЕЛЬНЫЙ РЕЗУЛЬТАТ

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

Однако моя модель выглядит так:

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

Каждая строка имеет entityId и реквизит, который является самим объектом. Этот шаблон не работает, но как бы его изменить, чтобы создать нужную таблицу выше?

EDIT: props в этом примере name и lastLogin, но мне нужно решение, которое не зависит от того, что содержится внутри props.

У меня есть FIDDLE.

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>
4b9b3361

Ответ 1

Вы всегда можете создать обработчик привязки для обработки преобразования.

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 };
  }
};

Затем примените его:

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

Ответ 2

В современном браузере (или с соответствующим polyfill) вы можете перебирать Object.keys(obj) (метод возвращает только собственные перечислимые свойства, что означает, что существует нет необходимости в дополнительной проверке hasOwnProperty):

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

Спрятано.

NB: Мне было просто интересно узнать, будет ли это работать, тело шаблона выше более загрязнено, чем то, что я хотел бы использовать на производстве (или вернуться к нескольким месяцам позже и быть похожим на "wtf" ).

Пользовательская привязка была бы лучшим вариантом, но мое личное предпочтение было бы использовать вычисленный наблюдаемый или записываемый вычисленный наблюдаемый (последний был бы полезен при работе с json ответы a-la restful api).

Ответ 3

Это модификация ответа Джеффа, с сохраненным контекстом привязки

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 };
    }
};

Теперь примените с родителем и корнем:

<table>
    <tbody data-bind="foreach: data">
        <tr data-bind="eachProp: props">
            <td data-bind="text: value, click: $root.doSomething"></td>
        </tr>
    </tbody>
</table> 

Ответ 4

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

Надеюсь, что это поможет (простите краткость)

Приложение:

Вот рабочий пример, который тестировался...

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

Ответ 5

Предположительно, существует более глубокая проблема (видеть этот поток в группах Google), то есть, что foreach рассматривает объект как словарь параметров, а не как сбор для итерации.

Мое лучшее решение до сих пор заключается в объединении foreach в Object.keys(myobject) и 'с' контекстом привязки.

Ответ 6

(не строго повторяя свойства, но делает таблицу выше)

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

обновлено: http://jsfiddle.net/cwnEE/7/

Ответ 7

Упрощенный ответ для работы с любым базовым объектом, который работал у меня:

<!-- ko foreach: {data: Object.keys(myObj)} -->
    <span data-bind="text: $data"></span> 
    <span data-bind="text: $parent.myObj[$data]"></span>
<!-- /ko -->