【问题标题】:Knockout validation - computed observable/dropdown list淘汰赛验证 - 计算的可观察/下拉列表
【发布时间】:2017-09-14 10:09:01
【问题描述】:

我的淘汰赛应用中有一个下拉列表,标记如下:

查看:

<select data-bind="options: $root.counties, optionsText: 'name', value: county, optionsCaption: '@PlanStrings.ChooseFromDropdownMessage'"></select>

我希望验证这一点以确保选择了一个选项。 它在浏览器中呈现如下:

<select data-bind="options: $root.counties, optionsText: 'name', value: county, optionsCaption: 'Choose...'">
    <option value="">Choose...</option>
    <option value="">Carlow</option>
    <option value="">Cavan</option>
    <option value="">Clare</option>
    <option value="">Cork</option>
    <option value="">Donegal</option>
    <option value="">Dublin</option>
    <option value="">Galway</option>
    <option value="">Kerry</option>
    <option value="">Kildare</option>
    <option value="">Kilkenny</option>
    <option value="">Laois</option>
    <option value="">Leitrim</option>
    <option value="">Limerick</option>
    <option value="">Longford</option>
    <option value="">Louth</option>
    <option value="">Mayo</option>
    <option value="">Meath</option>
    <option value="">Monaghan</option>
    <option value="">Offaly</option>
    <option value="">Roscommon</option>
    <option value="">Sligo</option>
    <option value="">Tipperary</option>
    <option value="">Waterford</option>
    <option value="">Westmeath</option>
    <option value="">Wexford</option>
    <option value="">Wicklow</option>
</select>

county 是一个计算出来的 observable,代码如下:

/// <summary>Constructor function for site info view model</summary>
function SiteModel(data, parent) {
    var self = this;
    // some code etc
    self.county = ko.computed({
        read: function () {
            return ko.utils.arrayFirst(
                    parent.counties(),
                    i => i.countyId() === self.countyId()
            );
        },
        write: function (value) {
            self.countyId(value === undefined ?
                null : value.countyId());
        }
    });
}

在整个 ViewModel() 函数中调用 SiteModel。

我已经在同一页面上的一个简单输入文本字段(非计算)上实现了 ko 验证,因此设置 ko 验证不是更基本的问题。我只是在做错事。验证这个计算出来的 observable/dropdown。

我已经尝试了以下所有方法:

self.countyId = ko.observable().extend({ required: { message: "You must select a county." } });
self.county.extend({ required: { message: "You must select a county." } });
self.countyId.extend({ required: { message: "You must select a county." } });
self.county = ko.computed({
    read: function () {
        return ko.utils.arrayFirst(
                parent.counties(),
                i => i.countyId() === self.countyId()
        );
    },
    write: function (value) {
        self.countyId(value === undefined ?
            null : value.countyId());
    }
}).extend({ required: { message: "You must select a county." } });

这些似乎都不起作用。 新来的 ko 所以对此一头雾水。 有关如何验证此下拉/计算 observable 的任何建议?

我可能会补充一点,此下拉菜单的所有其他方面都可以正常工作,因为它在保存页面时会正确读取值,并且如果模型包含值,则使用模型中的值作为选定值呈现。

##EDIT## 包括完整的 View 模型代码以确保完整性:

function ($, ko, validation, mapping, formatting, strings, global, base) {

    /// <summary>Constructor function for top-level view model</summary>
    function ViewModel(data) {
        var self = this;
        var map = {
            ignore: ["RegistrationDate"],
            sites: {
                create: function (options) {
                    return new SiteModel(options.data, self);
                }
            }
        };
        mapping.fromJS(data, map, self);

        self.industrySector = ko.computed({
            read: function () {
                return ko.utils.arrayFirst(
                    self.industrySectors(),
                    i => i.industrySectorId() === self.industrySectorId()
                );
            },
            write: function (value) {
                self.industrySectorId(value === undefined ?
                    null : value.industrySectorId());
            }
        });

        self.isEditingNumberOfEmployees = ko.observable(false);
        self.editNumberOfEmployees = () => self.isEditingNumberOfEmployees(true);
        self.numberOfEmployeesFormatted =
            ko.computed(() => {
                var value = ko.unwrap(self.numberOfEmployees);
                return value === null ?
                    strings.nullPlaceholder :
                    value;
            });

        self.isEditingTurnover = ko.observable(false);
        self.editTurnover = () => self.isEditingTurnover(true);
        self.turnoverFormatted =
            ko.computed(() => {
                var value = ko.unwrap(self.turnover);
                if (value !== null)
                {
                    var val_string = value.toString();
                    var value_parsed = parseFloat(val_string.replace(/[^\d\.]/g, ''));
                }
                return value === null ?
                    strings.nullPlaceholder :
                    formatting.formatDecimal(value_parsed);
            });

        self.isEditingYearEstablished = ko.observable(false);
        self.editYearEstablished = () => self.isEditingYearEstablished(true);
        self.yearEstablishedFormatted =
            ko.computed(() => {
                var value = ko.unwrap(self.yearEstablished);
                return value === null ?
                    strings.nullPlaceholder :
                    value;
            });

        self.shouldShowLogoMessage = ko.observable(true);

        var companyLogoId = data.logoFileId;

        if (companyLogoId > 0)
        {
            self.shouldShowLogoMessage(false);
        }


        self.uploadLogo = function (data, event) {
            var inputId = $(event.target).data("inputId");
            $("#" + inputId).trigger("click");
        };

        self.liveSites = ko.computed(function () {
            return ko.utils.arrayFilter(
                self.sites(),
                site => ko.unwrap(site.state) !== "Deleted"
            );
        });

        self.addSite = function () {
            var newSite = new SiteModel(
                {
                    name: null,
                    address1: null,
                    address2: null,
                    address3: null,
                    countyId: null,
                    keyActivities: null,
                    state: "Added"
                },
                self
            );
            self.sites.push(newSite);
            self.setTab(newSite.position());
        }

        self.setTab = function (tabIndex) {
            // FIXME Investigate replacing with KO microtask when upgraded
            // to KO 3.4
            window.setTimeout(function () {
                $("#sites_tab").tabs("option", "active", tabIndex);
            }, 10);
        };

        self.siteCount = ko.computed(() => self.liveSites().length);

        self.dirtyFlag = new ko.dirtyFlag(self, false);

    }

    var knockoutValidationSettings = {
        insertMessages: true,
        messagesOnModified: true,
    };

    ko.validation.init(knockoutValidationSettings, true);

    ViewModel.prototype = base;


    /// <summary>Constructor function for site info view model</summary>
    function SiteModel(data, parent) {
        var self = this;

        mapping.fromJS(data, {}, self);

        self.position = ko.computed(function () {
            return parent.sites().indexOf(self);
        });

        self.hasData = function () {
            return (self.name() !== "" ||
                self.address1() !== "" ||
                self.address2() !== "");
        };

        self.displayName = ko.computed({
            read: function () {
                if (self.name() === "" || self.name() === null) {
                    return strings.introductionSiteAutoName + " " + (parent.liveSites ?
                        parent.liveSites().indexOf(self) + 1 :
                        parent.sites().indexOf(self) + 1).toString();
                }
                return self.name();
            },
            write: function (value) {
                self.name(value);
            }
        });

        self.anchorRef = ko.computed(function () {
            return "#tabs-" + (self.position() + 1).toString();
        });

        self.idRef = ko.computed(function () {
            return "tabs-" + (self.position() + 1).toString();
        });

        self.stateModified = ko.computed(function () {
            self.name();
            self.address1();
            self.address2();

            if (self.state() === "Unchanged") {
                self.state("Modified");
            }
        });

        self.deleteSite = function () {
            var pos = self.position();
            // FIXME: Replace with proper dialog box
            if (window.confirm(strings.introductionConfirmDeleteSiteMessage)) {
                if (self.state() === "Added") {
                    // Site was never saved to DB so just remove it
                    parent.sites().splice(pos, 1);
                }
                self.state("Deleted");
                if (pos > 0) pos--;
                parent.setTab(pos);
            }
        }

        self.county = ko.computed({
            read: function () {
                return ko.utils.arrayFirst(
                        parent.counties(),
                        i => i.countyId() === self.countyId()
                );
            },
            write: function (value) {
                self.countyId(value === undefined ?
                    null : value.countyId());
            }
        }).extend({ required: true });

        self.countyHidden = ko.computed({
            read: function () {
                return ko.utils.arrayFirst(
                        parent.counties(),
                        i => i.countyId() === self.countyId()
                );
            },
            write: function (value) {
                self.countyId(value === undefined ?
                    null : value.countyId());
            }
        }).extend({ required: true });

        self.name.extend({ required: { message: "You must enter a name." } });

    }

    return {
        ViewModel: ViewModel,
    };
}

【问题讨论】:

  • @RoyJ ive 在视图中的 select data-bind 中应用了该选项,现在它返回以下错误: Uncaught TypeError: parent.counties is not a function 这发生在 ko 内的读取中。计算函数。这似乎仅在我尝试将 .extend 应用于此计算时才会发生。
  • 看起来好像没有在顶层定义counties,这就是parent所指向的,对吧?
  • @RoyJ parent 从 ViewModel 函数中引用 self。我已经对其进行了调试,并且 parent.counties() 似乎确实包含数据。在 ViewModel(data) 中有一个名为counties 的数组,其中包含用于填充下拉选项的数据。从某种意义上说,下拉菜单完美地工作,它填充了所有必需的选项,并且在页面加载时选择了记录的选项。我想要的是它识别何时选择“选择...”并指示必须选择一个选项。看起来应该很简单,但它让我发疯!
  • 仍然无法通过选择下拉菜单解决此问题,因此我添加了一个隐藏字段并将其绑定到计算的县对象中使用的县 ID 值。因此,当下拉列表更改时,隐藏字段的值也会随之更改,并且该字段上有一个必需的扩展器。因此,从所有意图和目的来看,验证看起来和行为都像是属于下拉列表。它是一个黑客,但让我解决了这个问题。感谢您的帮助@RoyJ :)

标签: knockout.js knockout-validation


【解决方案1】:

您需要以不同的方式使用select 绑定。 (optionsValue)

将所选值更新为countyId 而不是county 那么你可以得到county,因为computed observable 依赖于countyId

var data = { counties: [{ countyId: 1, name: 'Carlow' },{ countyId: 2, name: 'Donegal' },{ countyId: 3, name: 'Galway' },{ countyId: 4, name: 'Kildare' },{ countyId: 5, name: 'Laois' }], countyId: 2 };

function vm(data) {
  var self = this;
  self.counties = ko.observable(data.counties);
  self.countyId = ko.observable(data.countyId).extend({ required: true });
  self.contry = ko.computed(function() {
    return ko.utils.arrayFirst(this.counties(), i => i.countyId == this.countyId());
  }, self);
}

ko.applyBindings(new vm(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout-validation/2.0.3/knockout.validation.min.js"></script>
<select data-bind="options: $root.counties, optionsText: 'name', optionsValue: 'countyId', value: countyId, optionsCaption: 'choose..'"></select>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

【讨论】:

  • 谢谢@Jag 我会试一试的。我可以问一下
     标记与我的特定问题有关的用途吗?您的解决方案是否需要它才能工作?
  • pre 标签只是显示来自ko(而不是console)的原始数据,解决方案不需要它。
猜你喜欢
  • 1970-01-01
  • 2012-11-02
  • 2013-08-26
  • 2017-09-19
  • 1970-01-01
  • 1970-01-01
  • 2023-03-20
  • 2014-11-05
  • 2012-09-08
相关资源
最近更新 更多