具有验证的Knockout-JS多步骤表单

时间:2013-05-23 19:15:54

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

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

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

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

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. 将所有字段最初声明为observable并将它们聚合成validatedObservables()是否有意义?

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

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

  4. 我是否正确地解决了这个问题?

    感谢您的任何澄清。

2 个答案:

答案 0 :(得分:5)

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

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

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

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

  4. 要重置表单,请执行以下操作:

    <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对象管理所有内容。

答案 1 :(得分:0)

请看Carl Schroedl的ValidatedViewModel

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

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

(我建议在应用/删除约束组时使用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>