KO - single page form
Is this a correct way of structuring a view model for a multi-step form?
by Leonardo Trimarchi
HTML
<script src="http://knockoutjs.com/downloads/knockout-2.2.1.js"></script>
<script src="http://cdn.strategiqcommerce.com/ajax/libs/knockout-validation/1.0.2/knockout.validation.js"></script>
<script src="http://www.sessiondirect.co.uk/scripts/knockout-ValidatedViewModel.js"></script>
<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>
CSS
ul {
list-style:none;
}
.nav-buttons {
padding:30px;
}
.nav-buttons a {
padding-right:15px;
}
.validationElement {
border: 1px solid red !important; }
.validationMessage {
font-family: Verdana, Arial, Helvetica, sans-serif;
color: red !important;
background: #fff5f5;
width: 175px;
padding: 5px !important;
margin-left: 10px !important;
margin-top: -505px;
margin-bottom: -500px;
margin-right: -500px;
font-size: 12px !important;
font-weight: normal !important;
line-height: 15px !important;
background-color: #fff5f5;
border: 1px solid #ff8686;
border-radius: 4px;
display: inline-block;
vertical-align: middle;
position: relative;
z-index: 999999;
-webkit-box-shadow: 1px 1px 1px 1px #eeeeee;
box-shadow: 1px 1px 1px 1px color;
/* arrow */ }
.validationMessage:before, .validationMessage:after {
z-index: 999999;
position: absolute;
top: 50%;
left: -8px;
margin-top: -5px;
content: '';
width: 0;
height: 0;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-right: 8px solid #ff8686; }
.validationMessage:before {
border-right-color: #ff8686;
left: -9px; }
JavaScript
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());