Я делаю эту нокаутирующую привязку неправильно?

Код просмотра:

<ul data-bind="foreach: BackColorOptions">
    <li data-bind="css: { selected: Selected }">
        <label>
            <input type="radio" name="BackColorOption" 
                data-bind="value: Color, checked: $root.BackColor" />
        </label>
    </li>
</ul>
@{
    var jsonModel = new System.Web.Script.Serialization.
        JavaScriptSerializer().Serialize(Model);
}
<input type="hidden" id="JsonModel" value='@jsonModel' />

Код viewmodel:

var initialData = $.parseJSON($('#JsonModel').val());

function BackColorOption(data, parent) {
    var self = this;
    self.parent = parent;
    self.Text = ko.observable(data.Text);
    self.Color = ko.computed(function () {
        return '#' + self.Text().toLowerCase();
    });
    self.Selected = ko.computed(function () {
        var backColor = self.parent.BackColor();
        if (backColor) {
            return backColor.toLowerCase() == self.Color;
        }
        return false;
    });
}

function TestViewModel() {
    var self = this;

    self.BackColor = ko.observable(initialData.BackColor);

    var mappedBackColorOptions = $.map(initialData.BackColorOptions, 
        function (item) {
            return new BackColorOption(item, self);
        }
    );
    self.BackColorOptions = ko.observableArray(mappedBackColorOptions);

}

ko.applyBindings(new TestViewModel());

Код модели:

string BackColor { get; set; }
SelectListItem[] BackColorOptions
{
    get
    {
        return new[] 
        { 
            new SelectListItem{Text = "cc0000"},
            new SelectListItem{Text = "ff9900"},
            new SelectListItem{Text = "dddd33"},
            new SelectListItem{Text = "009900"},
            new SelectListItem{Text = "00cccc"},
            new SelectListItem{Text = "0066ff"},
            new SelectListItem{Text = "9900ff"},
            new SelectListItem{Text = "ff00ff"},
        };
    }
}

Приведенный выше код работает, как и ожидалось, в IE (8) и Chrome (17), но не в FF (10.0.2). В основном я пытаюсь сделать селектор цвета, похожий на ярлыки проблем GitHub. Представление отображает набор переключателей, на которые можно нажать, чтобы выбрать цвет. Когда радио проверено, я добавляю selected css class к родительскому <li>. Класс css заставляет значок галочки появляться над <li>.

В Firefox выбранный класс css применяется только после того, как пользователь просмотрел и проверил каждый переключатель хотя бы один раз. Я провел отладку и обнаружил, что это связано с тем, как вычисляемая наблюдаемая self.Color оценивается в замыкании BackColorOption. Перед первой проверкой радиостанции typeof(self.Color) == 'function' принимает значение true. Однако после проверки typeof(self.Color) == 'string' оценивается как true.

Это поведение typeof(self.Color) одинаково для Firebug и js-отладчика Chrome. Однако проблема в FF связана с этой строкой в ​​self.Selected, вычисленной наблюдаемой в закрытии BackColorOption:

return backColor.toLowerCase() == self.Color;

Chrome и IE по-прежнему возвращают true, даже если self.Color является функцией, а не строкой. Однако Firefox этого не делает. Когда self.Color является функцией, она возвращает false. Вот почему вы должны проверить каждое радио по крайней мере один раз, прежде чем класс css будет добавлен в <li> и появится значок.

Я все еще немного новичок в нокауте и, возможно, неправильно вызываю свойство модели представления как функцию, когда это предполагается. Я все еще немного не понимаю, когда использовать скобки (), а когда их опускать. Есть ли другой способ написать вычисляемую наблюдаемую self.Selected, которая зависит от вычисляемой наблюдаемой self.Color (в замыкании BackColorOption)?

Обновление 1

Мне удалось заставить это работать в FF 10.0.2 со следующим:

self.Selected = ko.computed(function () {
    var backColor = self.parent.BackColor();
    var selfColor = self.Color;
    if (typeof (selfColor) === 'function')
        selfColor = self.Color();
    if (backColor) {
        return backColor.toLowerCase() === selfColor;
    }
    return false;
});

Тем не менее, это похоже на то, что я борюсь с нокаутом. Разве это не должно «просто работать»?


person danludwig    schedule 13.03.2012    source источник
comment
Не могли бы вы создать jsfiddle, чтобы продемонстрировать это?   -  person madcapnmckay    schedule 13.03.2012
comment
Вот скрипт: jsfiddle.net/JUKGh/2   -  person danludwig    schedule 13.03.2012


Ответы (1)


Привязка value в KO на самом деле не идеальна для переключателей и флажков. В вашей ситуации вам нужно, чтобы ваши переключатели имели атрибут value, чтобы при нажатии на них это значение можно было использовать для обновления наблюдаемого TestViewModel.BackColor.

Обычно с переключателями вы не хотите, чтобы атрибут value менялся (если вообще когда-либо) после рендеринга html.

Итак, я изменил ваш HTML-шаблон с привязки value на использование привязки attr (атрибут html). Теперь это просто установка атрибута value html ваших переключателей. Затем привязка checked синхронизирует ваш наблюдаемый TestViewModel.BackColor с любым value переключателя, который отмечен.

См. эту скрипту: http://jsfiddle.net/m2KQ2/

Кроме того, в строке вашей функции BackColorOption есть опечатка:

self.Selected = ko.computed(function () {
    var backColor = self.parent.BackColor();
    if (backColor) {
        return backColor.toLowerCase() == self.Color; //<-- should be Color();
    }
    return false;
});
person ericb    schedule 13.03.2012
comment
Спасибо, Эрик, да, это работает. Мне не приходило в голову привязать значение, используя привязку attr, а не привязку значения. Вы совершенно правы, я не хочу, чтобы значение переключателя менялось. Я заметил, что ваша скрипка вызывает self.Color() как функцию, и соответствующим образом обновил мой код. - person danludwig; 13.03.2012