Как использовать нокаут для итерации над объектом (а не массивом)
Я хочу использовать нечто похожее на конструкцию нокаута 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>
Ответы
Ответ 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 -->