Looking for a sanity check here. I've recently started learning knockout, and have been instructed to convert an existing multi-step form.
The basic idea is to validate each step before allowing the user to continue. There are also certain restrictions set up (not shown) that determine whether to continue onward or submit using all of the current data (ex: if they don't qualify).
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'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());
My questions:
Does it make sense to declare all of the fields initially as observables and then cluster them together into validatedObservables() ?
If at the end I want to submit the entire form, is there a smarter way of accomplishing this than concatenating each step using ko.toJSON(self.step1()). Would I need to create a "full form" observable that contains all of the input observables? In other words, what's the best way to serialize the full form? Would I want to use ko.toJSON(self) ?
What's the best way to reset the form to the initial configuration? Is there a way of re-applying ko.applyBindings(new myViewModel()) ?
Am I going about this correctly?
Thanks for any clarification.
It's a good start. I suggest you manage visibility using knockout and turn to jQuery only when there is no other option. By that I mean managing visibility of fieldsets:
<fieldset data-bind="visible: currentStep() === 1">
Yes, it does make sense to have all fields as observables initially. Good strategy is to get your data as JSON from server and use mapping plugin to convert everything to observables. If you prefer to code everything by hand, that's OK.
At the end just submit whole view model: ko.toJSON(self) will do the job serializing it to JSON. You may want to convert it to JS object: ko.toJS, then cleanup data that you don't want to submit (e.g. lookup data and etc.) and then use JSON.stringify to convert to JSON.
It's hard to reset validation state using validation plugin. To reset the form, just remove existing form from the DOM and applyBindings on the new HTML. Keep HTML somewhere handy on the page:
To reset form then 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'));
Form tag is not necessary in this case, since you manage everything using JS objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With