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...