0

Using knockout, I have a select (a list of names) whose options are bound to another set of knockout-bound data (people). When the name of any person changes, the value of the select option that is bound to that person's name is correctly updated. However, the select's selection is not preserved if you had that person selected already.

See this jsFiddle for a live example: http://jsfiddle.net/DbBZQ/

  1. Select "Jane" from the list.
  2. Change the name "Jane" to something else ("Jane Doe" for example).
  3. Notice the select defaults back to the first item.

How can I make the selection stick to the same option index even if the underlying value has changed? Is there a way to instruct knockout to preserve the selection or do I have to do this separately using JS?

Complete Code Example

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title></title>
        <script type="text/javascript" src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script type="text/javascript">
            var data =
            {
                people: ko.observableArray(
                [
                    { name: ko.observable("Jim") },
                    { name: ko.observable("Jane") },
                    {
                        name: ko.observable("Sam"),
                        subordinates: ko.observableArray(
                        [
                            { 
                        name: ko.observable("Tambone"),
                                subordinates: ko.observableArray(
                                [
                                    { name: ko.observable("Edward") },
                                    { name: ko.observable("Kristy") },
                                    { name: ko.observable("Thomas") },
                                    { name: ko.observable("Andy") }
                                ])
                            },
                            { name: ko.observable("Jules") }
                        ])
                    }
                ])
            };
            var allNames = ko.computed(function ()
            {
                var names = [];
                var selector = function (name, indent)
                {
                    var option =
                    {
                        value: name,
                        text: (indent || "") + name
                    };

                    return option;
                };

                for (var i = 0; i < data.people().length; i++)
                {
                    names.push(selector(data.people()[i].name()));
                    addSubordinates(names, 1, data.people()[i].subordinates, selector);
                }

                return names;
            });

            function addSubordinates(names, depth, subordinates, selector)
            {
                if (subordinates != null)
                {
                    var indentText = "";

                    for (var i = 0; i < depth; i++)
                        indentText += ". . ";

                    for (var i = 0; i < subordinates().length; i++)
                    {
                        names.push(selector(subordinates()[i].name(), indentText));
                        addSubordinates(names, depth + 1, subordinates()[i].subordinates, selector);
                    }
                }
            }
        </script>
    </head>
    <body>
        <div data-bind="foreach: data.people">
            <input type="text" data-bind="value: name" /><br />
        </div>
        <a href="JavaScript:data.people.push({ name: ko.observable('New Person') });">Add Person</a>

        <br /><br /><br />

        <select data-bind="options: allNames, optionsValue: 'value', optionsText: 'text', optionsCaption: 'All Names...'" />

        <script type="text/javascript">
            ko.applyBindings();
        </script>
    </body>
</html>
4

1 回答 1

1

选择丢失的原因是因为选择的值直接与 name 属性匹配,而 name 属性会发生变化。因此,所选值不再存在于数据源 (allNames) 中。

如果您想保留选择,您有几个选择:

  1. 实现一个 hack,例如跟踪索引,并在值更改后重置它
  2. 将选定的值绑定到不变的属性。

您是否有可以用作选定值的不可变属性?

作为示例,我向数据源中的对象添加了一个 id 属性,并将其用作选定值而不是名称。这按您期望的方式工作:

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta charset="utf-8" />
        <title></title>
        <script type="text/javascript" src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
        <script type="text/javascript">
            var data =
            {
                people: ko.observableArray(
                [
                    { id: 1, name: ko.observable("Jim") },
                    { id: 2, name: ko.observable("Jane") },
                    {
                        id: 3, name: ko.observable("Sam"),
                        subordinates: ko.observableArray(
                        [
                            { 
                        id: 4, name: ko.observable("Tambone"),
                                subordinates: ko.observableArray(
                                [
                                    { id: 5, name: ko.observable("Edward") },
                                    { id: 6, name: ko.observable("Kristy") },
                                    { id: 7, name: ko.observable("Thomas") },
                                    { id: 8, name: ko.observable("Andy") }
                                ])
                            },
                            { id: 9, name: ko.observable("Jules") }
                        ])
                    }
                ])
            };
            var allNames = ko.computed(function ()
            {
                var names = [];
                var selector = function (id, name, indent)
                {
                    var option =
                    {
                        value: id,
                        text: (indent || "") + name
                    };

                    return option;
                };

                for (var i = 0; i < data.people().length; i++)
                {
                    names.push(selector(data.people()[i].id, data.people()[i].name()));
                    addSubordinates(names, 1, data.people()[i].subordinates, selector);
                }

                return names;
            });

            function addSubordinates(names, depth, subordinates, selector)
            {
                if (subordinates != null)
                {
                    var indentText = "";

                    for (var i = 0; i < depth; i++)
                        indentText += ". . ";

                    for (var i = 0; i < subordinates().length; i++)
                    {
                        names.push(selector(subordinates()[i].id,subordinates()[i].name(), indentText));
                        addSubordinates(names, depth + 1, subordinates()[i].subordinates, selector);
                    }
                }
            }
        </script>
    </head>
    <body>
        <div data-bind="foreach: data.people">
            <input type="text" data-bind="value: name" /><br />
        </div>
        <a href="JavaScript:data.people.push({ name: ko.observable('New Person') });">Add Person</a>

        <br /><br /><br />

        <select data-bind="options: allNames, optionsValue: 'value', optionsText: 'text', optionsCaption: 'All Names...'" />

        <script type="text/javascript">
            ko.applyBindings();
        </script>
    </body>
</html>

编辑: 作为替代方案,如果您将value属性设置为返回项目索引的 ko.computed 怎么办?像这样:

var allNames = ko.computed(function ()
{
    var names = [];
    var selector = function (item, name, indent)
    {
        var option =
        {
            value: ko.computed(function(){ return data.people().indexOf(item);}),
            text: (indent || "") + name
        };

        return option;
    };

    for (var i = 0; i < data.people().length; i++)
    {
        names.push(selector(data.people()[i], data.people()[i].name()));
        addSubordinates(names, 1, data.people()[i].subordinates, selector);
    }

    return names;
});

function addSubordinates(names, depth, subordinates, selector)
{
    if (subordinates != null)
    {
        var indentText = "";

        for (var i = 0; i < depth; i++)
            indentText += ". . ";

        for (var i = 0; i < subordinates().length; i++)
        {
            names.push(selector(subordinates()[i],subordinates()[i].name(), indentText));
            addSubordinates(names, depth + 1, subordinates()[i].subordinates, selector);
        }
    }
}
于 2013-04-11T01:55:42.773 回答