JS multi-stage knockout form with validation

Look for common sense check. Recently, I began to study knockout, and I was instructed to transform the existing multi-stage form.

The basic idea is to check each step before allowing the user to continue. There are also certain restrictions established (not shown) that determine whether to continue or serve using all current data (for example: if they do not meet the criteria).

Here is a fiddle with a simplified version (the actual form contains about 40 fields over 4 steps)

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 First Name:</label> <input type="text" data-bind="value: referralFirst" required="required" /> </li> <li> <label for="password">Referrer 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()); 

My questions:

  • Does it make sense to first declare all fields as observables and then group them into validatedObservables ()?

  • If at the end I want to present the whole form, is there a smarter way to accomplish this than concatenating each step using ko.toJSON (self.step1 ()). Do I need to create a β€œfull form” observable that contains all input observables? In other words, what's the best way to serialize a full form? I would like to use ko.toJSON (self)?

  • What is the best way to reset forms for initial configuration? Is there a way to reapply ko.applyBindings (new myViewModel ())?

Am I doing it right?

Thanks for any clarification.

+4
source share
2 answers

This is a good start. I suggest you control visibility with a knockout and access jQuery only when there is no other option. By this I mean controlling the visibility of fields:

 <fieldset data-bind="visible: currentStep() === 1"> 
  • Yes, it makes sense to first have all the fields as observables. A good strategy is to get your data as JSON from the server and use the plugin to convert everything to observables. If you prefer to encode everything manually, that's fine.

  • In the end, just imagine a model of the whole kind: ko.toJSON (self) will serialize the job in JSON. You can convert it to a JS: ko.toJS object, then clear the data you do not want to send (e.g. search data, etc.), and then use JSON.stringify to convert to JSON.

  • It is difficult to reset the validation state using the validation plugin. In the reset form, simply remove the existing form from the DOM and applyBindings into the new HTML. Keep HTML somewhere convenient on the page:

In the reset form, do:

 <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')); 

In this case, the form tag is not needed as you control everything with JS objects.

+5
source

See ValidatedViewModel from Karl Shreda.

When used in conjunction with an excellent knockout verification plugin, you can create verification restriction groups and apply them as needed.

Each time you run your validation procedure, you must delete all restriction groups, and then apply the restriction groups that you want for this step. Alternatively, subscribe to the step observed to set up restriction groups.

(I suggest using the try / catch statement when applying / removing restriction groups, since it will be erroneous if the restriction group has already been applied / removed.)

A bit of a learning curve is attached to this point, but it really helped me create a basket / checkout page with an appropriate check at every step.

Update: Here is the updated jsfiddle using ValidatedViewModel . I made the visible step dependent on the current Step observable and removed the necessary tags. All validation is now processed in the model. As a bonus, CSS in jsfiddle also styles the validation message without additional markup.

 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 now looks like this:

 <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 First Name:</label> <input type="text" data-bind="value: referralFirst" /> </li> <li> <label for="password">Referrer 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> 
0
source

Source: https://habr.com/ru/post/1482452/


All Articles