【问题标题】:Knockout-JS Multi-Step Form with Validation带有验证的 Knockout-JS 多步骤表单
【发布时间】:2013-05-19 07:05:08
【问题描述】:

在这里寻找健全性检查。我最近开始学习淘汰赛,并被指示转换现有的多步表格。

基本思想是在允许用户继续之前验证每个步骤。还设置了某些限制(未显示),确定是继续前进还是使用所有当前数据提交(例如:如果他们不符合条件)。

这是一个简化版本的小提琴(实际表单包含大约 40 个字段,分 4 个步骤)

http://jsfiddle.net/dyngomite/BZcNg/

HTML:

<form id="register">
 <fieldset>
      <h2>About You</h2>
    <ul>
        <li>
            <label for="firstName">First Name:</label>
            <input type="text" data-bind="value: firstName" required="required" />
        </li>
        <li>
            <label for="lastName">Last Name</label>
            <input type="text" data-bind="value: lastName" required="required" />
        </li>
    </ul>
 </fieldset>
 <fieldset>
     <h2>Your Business</h2>

    <ul>
        <li>
            <label for="businessName">Business Name:</label>
            <input type="text" data-bind="value: businessName" required="required" />
        </li>
        <li>
            <label for="currentCustomer">Were you referred by someone?</label>
            <input type="checkbox" data-bind="checked: referred" />
        </li>
    </ul>
</fieldset>
<fieldset>
     <h2>User Info</h2>

    <ul>
        <li>
            <label for="userName">Referrer's First Name:</label>
            <input type="text" data-bind="value: referralFirst" required="required" />
        </li>
        <li>
            <label for="password">Referrer's Last Name:</label>
            <input type="password" data-bind="value: referralLast" required="required" />
        </li>
    </ul>
  </fieldset>
 </form>
<div class="nav-buttons"> <a href="#" data-bind='click: stepForward'>Continue</a>
    <a href="#" data-bind='click: stepBack'>Back</a>
    <a href="#" data-bind='click: resetAll'>Cancel</a>
 </div>

JS:

 $("#register").children().hide().first().show();

ko.validation.init({
   parseInputAttributes: true,
   decorateElement: true,
   writeInputAttributes: true,
   errorElementClass: "error"
});

function myViewModel() {

var self = this;

//observable init
self.firstName = ko.observable();
self.lastName = ko.observable();
self.businessName = ko.observable();
self.referred = ko.observable();
self.referralFirst = ko.observable();
self.referralLast = ko.observable();

//validaiton observable init
self.step1 = ko.validatedObservable({
    firstName: self.firstName,
    lastName: self.lastName
});

self.step2 = ko.validatedObservable({
    businessName: self.businessName,
    referred: self.referred
});

self.step3 = ko.validatedObservable({
    referralFirst: self.referralFirst,
    referralLast: self.referralLast
});

//navigation init
self.currentStep = ko.observable(1);

self.stepForward = function () {
    if(self.currentStep()<4){
        self.changeSection(self.currentStep() + 1);
    }
}

self.stepBack = function () {
    if (self.currentStep() > 1) {
        self.changeSection(self.currentStep() - 1);
    }
}

self.changeSection = function(destIdx){
    var validationObservable = "step" + self.currentStep();
    if(self[validationObservable]().isValid()){
        self.currentStep(destIdx);
        $("#register").children().hide().eq(self.currentStep() - 1).show();
        return true;
    }else{
        self[validationObservable]().errors.showAllMessages();
    }
    return false;
}

self.resetAll = function(){
    //TODO
    return false;
}

}

 ko.applyBindings(new myViewModel());

我的问题:

  1. 最初将所有字段声明为 observables 然后将它们聚集到一起验证的 Observables() 是否有意义?

  2. 如果最后我想提交整个表单,有没有比使用 ko.toJSON(self.step1()) 连接每个步骤更聪明的方法来完成此操作。我需要创建一个包含所有输入可观察对象的“完整形式”可观察对象吗?换句话说,序列化完整表单的最佳方式是什么? 我想使用 ko.toJSON(self) 吗?

  3. 将表单重置为初始配置的最佳方法是什么?有没有办法重新申请 ko.applyBindings(new myViewModel()) ?

我这样做对吗?

感谢您的澄清。

【问题讨论】:

    标签: knockout.js knockout-2.0 knockout-validation


    【解决方案1】:

    这是一个好的开始。我建议您使用淘汰赛管理可见性,只有在没有其他选择时才使用 jQuery。我的意思是管理字段集的可见性:

    <fieldset data-bind="visible: currentStep() === 1">
    
    1. 是的,最初将所有字段都作为可观察对象确实有意义。好的策略是从服务器获取 JSON 格式的数据,并使用映射插件将所有内容转换为 observables。如果您更喜欢手动编写所有代码,那没关系。

    2. 最后只需提交整个视图模型:ko.toJSON(self) 将完成将其序列化为 JSON 的工作。您可能想将其转换为 JS 对象:ko.toJS,然后清理您不想提交的数据(例如查找数据等),然后使用 JSON.stringify 转换为 JSON。

      李>
    3. 使用验证插件很难重置验证状态。要重置表单,只需从 DOM 中删除现有表单并在新 HTML 上应用绑定。将 HTML 放在页面上方便的位置:

    要重置表单然后执行:

    <script type="text/html" id="ko-template">
       <form id="register"> 
       ...
       </form>
    </script>
    
    <div id="context"></div>
    

    JavaScript:

    var template = $('#ko-template').html();
    
    $('#context').empty().html(template);
    
    ko.applyBindings(new myViewModel(), document.getElementById('context'));
    

    在这种情况下不需要表单标签,因为您使用 JS 对象管理所有内容。

    【讨论】:

    • 谢谢。我现在看到将可见性绑定到 currentStep() 是一种更简洁的方法。 1. 我计划在重新创建所有基本功能后集成映射插件。 2.我认为你是对的,我需要使用 ko.toJS() 然后限制传递的字段。 3.这很有趣,但有必要使用KO模板吗?在这种情况下,我将在脚本标记内以及 div#context 内包含表单标记。避免使用模板并仅将表单 HTML 保存在文档准备好的全局 var 中,然后按照您的其余程序进行操作是不是一个坏主意?
    【解决方案2】:

    看看 Carl Schroedl 的 ValidatedViewModel

    当与优秀的Knockout Validation plugin 结合使用时,您可以创建验证约束组并根据需要应用它们。

    在验证例程的每次运行中,您都将删除所有约束组,然后为给定步骤应用所需的约束组。或者订阅可观察的步骤以设置约束组。

    (我建议在应用/删除约束组时使用 try/catch 语句,因为如果已经应用/删除了约束组,则会出错。)

    这有一点学习曲线,但它确实帮助我创建了一个购物篮/结帐页面,并在每个步骤上进行了适当的验证。

    更新: Here is an updated jsfiddle using ValidatedViewModel。我使可见步骤依赖于 currentStep observable 并删除了所需的标签。现在所有验证都在模型中处理。作为奖励,jsfiddle 中的 CSS 还设置了验证消息的样式,无需额外的标记。

    ko.validation.init({
        parseInputAttributes: false,
        decorateElement: true,
        insertMessages: true,
        messagesOnModified: true,
        grouping: { deep: true, observable: true }
    });
    
    var myViewModel = ValidatedViewModel(function () {
        var self = this;
    
        //observable init
        self.firstName = ko.observable();
        self.lastName = ko.observable();
        self.businessName = ko.observable();
        self.referred = ko.observable();
        self.referralFirst = ko.observable();
        self.referralLast = ko.observable();
    
        //navigation init
        self.currentStep = ko.observable(1);
    
        self.stepForward = function () {
            if(self.currentStep()<4){
                self.changeSection(self.currentStep() + 1);
            }
        }
    
        self.stepBack = function () {
            if (self.currentStep() > 1) {
                self.changeSection(self.currentStep() - 1);
            }
        }
    
        self.changeSection = function(destIdx){
            //remove all constraint groups
            try { self.removeConstraintGroup('step1'); } catch (e) { }
            try { self.removeConstraintGroup('step2'); } catch (e) { }
            try { self.removeConstraintGroup('step3'); } catch (e) { }
    
            //apply constraint group for current step
            try{self.applyConstraintGroup('step' + self.currentStep());} catch(e){}
    
            var errorCount = self.errors().length;
    
            self.errors.showAllMessages();
            if(errorCount===0){
                self.currentStep(destIdx);
                return true;
            }
            return false;
        }
    
    
        self.constraintGroups = {
            step1: {
                firstName: { required: true },
                lastName: { required: true }
            },
            step2: {
                businessName: { required: true }
            },
            step3: {
                referralFirst: { required: true },
                referralLast: { required: true }
            }
    
        }
    
        self.resetAll = function(){
            //TODO
            return false;
        }
    
        this.errors = ko.validation.group(this);
    
    });
    
    ko.applyBindings(new myViewModel());
    

    HTML 现在看起来像这样:

    <form id="register">
        <h1>Current Step: <span data-bind="text:currentStep()"></span></h1>
        <fieldset data-bind="visible: currentStep()===1">
             <h2>About You</h2>
    
            <ul>
                <li>
                    <label for="firstName">First Name:</label>
                    <input type="text" data-bind="value: firstName"  />
                </li>
                <li>
                    <label for="lastName">Last Name</label>
                    <input type="text" data-bind="value: lastName"  />
                </li>
            </ul>
        </fieldset>
        <fieldset data-bind="visible:currentStep()===2">
             <h2>Your Business</h2>
    
            <ul>
                <li>
                    <label for="businessName">Business Name:</label>
                    <input type="text" data-bind="value: businessName"  />
                </li>
                <li>
                    <label for="currentCustomer">Were you referred by someone?</label>
                    <input type="checkbox" data-bind="checked: referred" />
                </li>
            </ul>
        </fieldset>
        <fieldset data-bind="visible:currentStep()===3">
             <h2>User Info</h2>
    
            <ul>
                <li>
                    <label for="userName">Referrer's First Name:</label>
                    <input type="text" data-bind="value: referralFirst"  />
                </li>
                <li>
                    <label for="password">Referrer's Last Name:</label>
                    <input type="password" data-bind="value: referralLast"  />
                </li>
            </ul>
        </fieldset>
    </form>
    <div class="nav-buttons"> <a href="#" data-bind='click: stepForward'>Continue</a>
     <a href="#" data-bind='click: stepBack'>Back</a>
     <a href="#" data-bind='click: resetAll'>Cancel</a>
    
    </div>
    

    【讨论】:

    • 这是一种有趣的方法,但我并没有真正看到使用 validViewModel 比将各个步骤聚集到一个 validObservable 中然后调用 isValid() 方法的好处。最终结果似乎是一样的,不需要 try/catch,不是吗?
    • 另外,我设置了 parseInputAttributes = true 并保持我的验证约束内联,以便在可用时利用本机浏览器行为,但这是个人喜好。
    • 这种方法是关于可扩展性的。如果模型很小并且您的验证要求相对基本,您的初始方法将可以正常工作。考虑当您需要不同的验证选项时会发生什么,例如密码/确认密码字段。你将如何实现解析属性?密码字段必须至少包含 8 个字符,并且密码和确认密码必须匹配。您还希望显示适当的验证消息。使用约束组,这是干净整洁的,并且与视图分开 - 但没有它会变得非常棘手。
    • 这个想法非常好,虽然我不明白你为什么要创建一个函数来处理步骤,因为你现在基本上是硬编码步骤。为什么不订阅 currentStep() 然后根据 newValue 进行相应的更改?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 2019-08-30
    • 2020-01-20
    • 1970-01-01
    • 1970-01-01
    • 2015-01-11
    相关资源
    最近更新 更多