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