Knockout Example - Complex Form Save / Load

Using json to submit form, also to pre populate, including opening panels where needed

HTML

<script src="https://flowcms.infxsolutions.com/script/jQuery/jquery-2.1.1.min.js"></script>
<form id='Form1'>
    <h3>Knockout.js Viewmodel demo, with some <a target=_blank href="http://json-schema.org/">'json-schema' validation</a></h3>The 'ckschema' function is a nody bit of code see TV4 for doing it properly. 
    <button class="loadclient" data-bind="click: loadclient">load form snapshot data</button>
    <button class="listelements" data-bind="click: listelements">list elements</button><br/>
        DEV debug window (shows validation messages as user enters data):
        <div data-bind="html: message"></div>
        <hr>
        <div class="formpanel">
        <h3>Client</h3>

        Forename:
        <input type="text" name='F_client.forename' placeholder="Forename" data-bind="value: forename" required />
        <br/>
        Surname:
        <input type='text' name='F_client.surname' data-bind="value: surname" required />
        <br/><label for="1234567">Sex</label>
        <input type="radio" id="1234567" name="sex" required value="male" />Male
        <input type="radio" name="sex" required value="female" />Female
        <br/>   
        
        Email:
        <input type='email' data-bind="value: email" required />
        <br/>
        House No.:
        <input type='text' data-bind="value: housenum" />
        <br/>
        Pay by:
        <select id='payby' data-bind="value:payby, valueAllowUnset:true">
            <option value="-1">[Select]</option>
            <option value="0">Cash</option>
            <option value="1">On Account</option>
            <option value="2">Credit Card</option>
        </select>
 
        <div data-bind="visible: payby()==0">
            Cash Amount:
            <input type='text' data-bind="value: amount" required />
            <br/>
        </div>
        <div data-bind="visible: payby()==1">
            Account Name:
            <input type='text' data-bind="value: accname" required />
            <br/>
    ...

CSS

body{line-height:26px;}

.formpanel {background-color:#F0F0FF;}

div {
    padding:20px;
    border:1px solid grey;
}
#payby {
    margin-top:30px;
    margin-bottom:30px;
}
.submit {margin-top:20px;}
input:required:invalid, input:focus:invalid {
    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAeVJREFUeNqkU01oE1EQ/mazSTdRmqSxLVSJVKU9RYoHD8WfHr16kh5EFA8eSy6hXrwUPBSKZ6E9V1CU4tGf0DZWDEQrGkhprRDbCvlpavan3ezu+LLSUnADLZnHwHvzmJlvvpkhZkY7IqFNaTuAfPhhP/8Uo87SGSaDsP27hgYM/lUpy6lHdqsAtM+BPfvqKp3ufYKwcgmWCug6oKmrrG3PoaqngWjdd/922hOBs5C/jJA6x7AiUt8VYVUAVQXXShfIqCYRMZO8/N1N+B8H1sOUwivpSUSVCJ2MAjtVwBAIdv+AQkHQqbOgc+fBvorjyQENDcch16/BtkQdAlC4E6jrYHGgGU18Io3gmhzJuwub6/fQJYNi/YBpCifhbDaAPXFvCBVxXbvfbNGFeN8DkjogWAd8DljV3KRutcEAeHMN/HXZ4p9bhncJHCyhNx52R0Kv/XNuQvYBnM+CP7xddXL5KaJw0TMAF8qjnMvegeK/SLHubhpKDKIrJDlvXoMX3y9xcSMZyBQ+tpyk5hzsa2Ns7LGdfWdbL6fZvHn92d7dgROH/730YBLtiZmEdGPkFnhX4kxmjVe2xgPfCtrRd6GHRtEh9zsL8xVe+pwSzj+OtwvletZZ/wLeKD71L+ZeHHWZ/gowABkp7AwwnEjFAAAAAElFTkSuQmCC);
   background-color:#FFF0F0;
    background-position: right top;
    background-repeat: no-repeat;
  }
  input:required:valid {
    background-image:...

JavaScript

ko.extenders.validateViaSchema = function(target, option) {
   
    // Set a "sub-observable" on the existing observable
    target.error = ko.observable();
  
   function ckschema(newValue){
        //debugger;
        //var ck = js_schema.properties[option];
        var bits=option.split('.');
        var ck=js_schema;
        console.log("TBD when bits array length > 1 this errors ")
        //if(bits.length>1){ console.log(bits);return}; // bomb out for now
       
        // what it does is recursively tunnel down schema
       // so friends.forename, will access 
       // properties.friends.items.properties.forename 
        for (var n=0;n<bits.length;n++){
            if(n==1) ck = ck.items;// add in 'items' when tunnelling down
            ck = ck.properties[bits[n]];
        }
        
        var msg = "validate '"+option+"' as '"+ck.type + "' value is '" + newValue + "'";
       if(vm){
            msg = (vm.message())?vm.message() +"<br/>"+ msg:msg;
            vm.message(msg);
       }
        target.error(msg);
        //alert("validate as '"+js_schema.properties[option].type + "' value is '" + newValue + "'");
    };

    ckschema(target());
    target.subscribe(ckschema);
    
    return target;
};

ko.extenders.number = function(target, message) {
    var msg = message || "Value must be a number";

    // Set a "sub-observable" on the existing observable
    target.error = ko.observable();

    function validate(val) {
        var hasError = isNaN(parseInt(val, 10));
        target.error(hasError ? msg : null);
    }

    validate(target());
    target.subscribe(validate);

    // Allow chaining
    return target;
};

var clientdata = {
    'forename':'Ben',
    'surname':'Clayton',
    'email':'[email protected]',
    'payby':1,
    'accname':'B Clayton',
    'accno':'123456789-987',
    'friends':[
        {id:1234,'forename':'Sid','surname':'Smith'},
        {id:789,'forename':'Harry','surname':'Harterleigh'}
    ]
};

//////  JSON SCHEMA...