下面的代码已简化,请参见小提琴:http://jsfiddle.net/QTUqD/7/

基本上,我在数据绑定下设置设备名称,但是我还需要指定用于发送到数据库的optionsValue,但是当我设置它时,显示数据绑定为空白。

<script id="extItems" type="text/html">
   <tr>
        <td data-bind="text: device() && device().name"></td>
    </tr>
</script>

<script id="editExts" type="text/html">
    <tr>
        <td>

        <select data-bind="options: $root.devicesForItem($data), optionsText: 'name', value: device, optionsValue: 'id'"></select>

        </td>
    </tr>
</script>




    window.ExtListViewModel = new function () {
    var self = this;
    window.viewModel = self;

    self.list = ko.observableArray();

    self.pageSize = ko.observable(10);
    self.pageIndex = ko.observable(0);
    self.selectedItem = ko.observable();
    self.extQty = ko.observable();
    self.devices = ko.observableArray();
    self.addressList = ko.observableArray(['addressList']);
    self.availableDevices = ko.computed(function() {
        var usedQuantities = {}; // for each device id, store the used quantity
        self.list().forEach(function(item) {
            var device = item.device();
            if (device) {
                usedQuantities[device.id] = 1 + (usedQuantities[device.id] || 0);
            }
        });

        return self.devices().filter(function(device) {
            var usedQuantity = usedQuantities[device.id] || 0;
            return device.qty > usedQuantity;
        });
    });

    // need this to add back item's selected device to its device-options,
    // and to maintain original order of devices
    self.devicesForItem = function(item) {
        var availableDevices = self.availableDevices();
        return self.devices().filter(function(device) {
            return device === item.device() || availableDevices.indexOf(device) !== -1;
        });
    }

    self.edit = function (item) {
        if($('#extMngForm').valid()) {
            self.selectedItem(item);
        }
    };

    self.cancel = function () {
        self.selectedItem(null);
    };

    self.add = function () {
        if($('#extMngForm').valid()) {
            var newItem = new Extension();
            self.list.push(newItem);
            self.selectedItem(newItem);
            self.moveToPage(self.maxPageIndex());
        }
    };
    self.remove = function (item) {

            if (confirm('Are you sure you wish to delete this item?')) {

                    self.list.remove(item);
                    if (self.pageIndex() > self.maxPageIndex()) {
                        self.moveToPage(self.maxPageIndex());
                    }

            }
            $('.error').hide();
    };
    self.save = function () {
        if($('#extMngForm').valid()) {
            self.selectedItem(null);
        };
    };

    self.templateToUse = function (item) {
        return self.selectedItem() === item ? 'editExts' : 'extItems';
    };

    self.pagedList = ko.dependentObservable(function () {
        var size = self.pageSize();
        var start = self.pageIndex() * size;
        return self.list.slice(start, start + size);
    });
    self.maxPageIndex = ko.dependentObservable(function () {
        return Math.ceil(self.list().length / self.pageSize()) - 1;
    });
    self.previousPage = function () {
        if (self.pageIndex() > 0) {
            self.pageIndex(self.pageIndex() - 1);
        }
    };
    self.nextPage = function () {
        if (self.pageIndex() < self.maxPageIndex()) {
            self.pageIndex(self.pageIndex() + 1);
        }
    };
    self.allPages = ko.dependentObservable(function () {
        var pages = [];
        for (i = 0; i <= self.maxPageIndex() ; i++) {
            pages.push({ pageNumber: (i + 1) });
        }
        return pages;
    });
    self.moveToPage = function (index) {
        self.pageIndex(index);
    };


};

ko.applyBindings(ExtListViewModel, document.getElementById('extMngForm'));

function Extension(extension, name, email, vmpin, device, macAddress, shipTo){
    this.extension = ko.observable(extension);
    this.name = ko.observable(name);
    this.email = ko.observable(email);
    this.vmpin = ko.observable(vmpin);
    this.device = ko.observable(device);
    this.macAddress = ko.observable(macAddress);
    this.shipTo = ko.observable(shipTo);
}

最佳答案

当您使用optionsValue时,KO将属性值写入您对value绑定的任何内容。因此,它现在将使用id而不是对象填充值。

有两种方法可以解决这种情况,即您需要值(用于发送到DB)和对象(用于绑定UI的其他部分)。

一个非常典型的解决方案是在您的对象上创建一个可计算的可观察对象,该对象采用当前选定的对象并返回ID。

因此,在您的Extension中,您将执行以下操作:

this.device = ko.computed({
    read: function() {
        var device = this.device.asObject();
        return device && device.id;
    },
    deferEvaluation: true, //deferring evaluation, as device.asObject has not been created yet
}, this);

//create this as a sub-observable, so it just disappears when we turn this into JSON and we are just left with the id to send to the DB
this.device.asObject = ko.observable(device);


然后删除optionsValue并将valuedevice.asObject绑定

在这种情况下,我添加了asObject子可观察项,因此当您将其转换为JSON(ko.toJSON)发送到服务器时,它只会掉落。关于此技术的唯一棘手的部分是,如果要从服务器加载现有数据,则需要使用选项中的适当选项填充asObject

这是一个示例:http://jsfiddle.net/rniemeyer/Q3PEv/

我使用的另一个选项是继续使用optionsValue,然后创建一个自定义绑定,以单独的可观察对象跟踪对象。这是一个自定义绑定,它为绑定到asObject的任何内容创建一个value子可观察子。这样,您实际上根本不需要在视图模型中弄乱它。

//when using optionsValue, still track the select object in a different observable
ko.bindingHandlers.valueAsObject = {
    init: function(element, valueAccessor, allBindingsAccessor) {
        var value = allBindingsAccessor().value,
            prop = valueAccessor() || 'asObject';

        //add an "asObject" sub-observable to the observable bound against "value"
        if (ko.isObservable(value) && !value[prop]) {
              value[prop] = ko.observable();
        }
    },
    //whenever the value or options are updated, populated the "asObject" observable
    update: function(element, valueAccessor, allBindingsAccessor) {
        var prop = valueAccessor(),
            all = allBindingsAccessor(),
            options = ko.utils.unwrapObservable(all.options),
            value = all.value,
            key = ko.utils.unwrapObservable(value),
            keyProp = all.optionsValue,
            //loop through the options, find a match based on the current "value"
            match = ko.utils.arrayFirst(options, function(option) {
               return option[keyProp] === key;
            });

        //set the "asObject" observable to our match
        value[prop](match);
    }
};


此处的示例:http://jsfiddle.net/rniemeyer/E2kvM/

10-05 20:51