Edit in JSFiddle

/*=============================================================================
	Author:			Eric M. Barnard - @ericmbarnard								
	License:		MIT (http://opensource.org/licenses/mit-license.php)		
																				
	Description:	Validation Library for KnockoutJS							
===============================================================================
*/
/*globals require: false, exports: false, define: false, ko: false */

(function (factory) {
    // Module systems magic dance.

    if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
        // CommonJS or Node: hard-coded dependency on "knockout"
        factory(require("knockout"), exports);
    } else if (typeof define === "function" && define["amd"]) {
        // AMD anonymous module with hard-coded dependency on "knockout"
        define(["knockout", "exports"], factory);
    } else {
        // <script> tag: use the global `ko` object, attaching a `mapping` property
        factory(ko, ko.validation = {});
    }
}(function ( ko, exports ) {

    if (typeof (ko) === undefined) { throw 'Knockout is required, please ensure it is loaded before loading this validation plug-in'; }

    // create our namespace object
    ko.validation = exports;;/*global ko: false*/

var defaults = {
	registerExtenders: true,
	messagesOnModified: true,
	errorsAsTitle: true,            // enables/disables showing of errors as title attribute of the target element.
	errorsAsTitleOnModified: false, // shows the error when hovering the input field (decorateElement must be true)
	messageTemplate: null,
	insertMessages: true,           // automatically inserts validation messages as <span></span>
	parseInputAttributes: false,    // parses the HTML5 validation attribute from a form element and adds that to the object
	writeInputAttributes: false,    // adds HTML5 input validation attributes to form elements that ko observable's are bound to
	decorateElement: false,         // false to keep backward compatibility
	decorateElementOnModified: true,// true to keep backward compatibility
	errorClass: null,               // single class for error message and element
	errorElementClass: 'validationElement',  // class to decorate error element
	errorMessageClass: 'validationMessage',  // class to decorate error message
	grouping: {
		deep: false,        //by default grouping is shallow
		observable: true    //and using observables
	}
};

// make a copy  so we can use 'reset' later
var configuration = ko.utils.extend({}, defaults);

configuration.html5Attributes = ['required', 'pattern', 'min', 'max', 'step'];
configuration.html5InputTypes = ['email', 'number', 'date'];

configuration.reset = function () {
	ko.utils.extend(configuration, defaults);
};

ko.validation.configuration = configuration;;ko.validation.utils = (function () {
	var seedId = new Date().getTime();

	var domData = {}; //hash of data objects that we reference from dom elements
	var domDataKey = '__ko_validation__';

	return {
		isArray: function (o) {
			return o.isArray || Object.prototype.toString.call(o) === '[object Array]';
		},
		isObject: function (o) {
			return o !== null && typeof o === 'object';
		},
		values: function (o) {
			var r = [];
			for (var i in o) {
				if (o.hasOwnProperty(i)) {
					r.push(o[i]);
				}
			}
			return r;
		},
		getValue: function (o) {
			return (typeof o === 'function' ? o() : o);
		},
		hasAttribute: function (node, attr) {
			return node.getAttribute(attr) !== null;
		},
		getAttribute: function (element, attr) {
			return element.getAttribute(attr);
		},
		setAttribute: function (element, attr, value) {
			return element.setAttribute(attr, value);
		},
		isValidatable: function (o) {
			return o && o.rules && o.isValid && o.isModified;
		},
		insertAfter: function (node, newNode) {
			node.parentNode.insertBefore(newNode, node.nextSibling);
		},
		newId: function () {
			return seedId += 1;
		},
		getConfigOptions: function (element) {
			var options = ko.validation.utils.contextFor(element);

			return options || ko.validation.configuration;
		},
		setDomData: function (node, data) {
			var key = node[domDataKey];

			if (!key) {
				node[domDataKey] = key = ko.validation.utils.newId();
			}

			domData[key] = data;
		},
		getDomData: function (node) {
			var key = node[domDataKey];

			if (!key) {
				return undefined;
			}

			return domData[key];
		},
		contextFor: function (node) {
			switch (node.nodeType) {
				case 1:
				case 8:
					var context = ko.validation.utils.getDomData(node);
					if (context) { return context; }
					if (node.parentNode) { return ko.validation.utils.contextFor(node.parentNode); }
					break;
			}
			return undefined;
		},
		isEmptyVal: function (val) {
			if (val === undefined) {
				return true;
			}
			if (val === null) {
				return true;
			}
			if (val === "") {
				return true;
			}
		},
		getOriginalElementTitle: function (element) {
			var savedOriginalTitle = ko.validation.utils.getAttribute(element, 'data-orig-title'),
				currentTitle = element.title,
				hasSavedOriginalTitle = ko.validation.utils.hasAttribute(element, 'data-orig-title');

			return hasSavedOriginalTitle ?
				savedOriginalTitle : currentTitle;
		},
		async: function (expr) {
			if (window.setImmediate) { window.setImmediate(expr); }
			else { window.setTimeout(expr, 0); }
		}
	};
}());;var api = (function () {

	var isInitialized = 0,
		configuration = ko.validation.configuration,
		utils = ko.validation.utils;

	return {
		//Call this on startup
		//any config can be overridden with the passed in options
		init: function (options, force) {
			//done run this multiple times if we don't really want to
			if (isInitialized > 0 && !force) {
				return;
			}

			//becuase we will be accessing options properties it has to be an object at least
			options = options || {};
			//if specific error classes are not provided then apply generic errorClass
			//it has to be done on option so that options.errorClass can override default
			//errorElementClass and errorMessage class but not those provided in options
			options.errorElementClass = options.errorElementClass || options.errorClass || configuration.errorElementClass;
			options.errorMessageClass = options.errorMessageClass || options.errorClass || configuration.errorMessageClass;

			ko.utils.extend(configuration, options);

			if (configuration.registerExtenders) {
				ko.validation.registerExtenders();
			}

			isInitialized = 1;
		},
		// backwards compatability
		configure: function (options) { ko.validation.init(options); },

		// resets the config back to its original state
		reset: ko.validation.configuration.reset,

		// recursivly walks a viewModel and creates an object that
		// provides validation information for the entire viewModel
		// obj -> the viewModel to walk
		// options -> {
		//      deep: false, // if true, will walk past the first level of viewModel properties
		//      observable: false // if true, returns a computed observable indicating if the viewModel is valid
		// }
		group: function group(obj, options) { // array of observables or viewModel
			options = ko.utils.extend(ko.utils.extend({}, configuration.grouping), options);

			var validatables = ko.observableArray([]),
			validatablesTemp = [],
			result = null,
			flagged = [],

            dispose = function () {
                if (options.deep) {
                    ko.utils.arrayForEach(flagged, function (obj) {
                        delete obj.__kv_traversed;
                    });
                    flagged = [];
                }
            },

			//anonymous, immediate function to traverse objects hierarchically
			//if !options.deep then it will stop on top level
			traverse = function traverse(obj, level) {
				var objValues = [],
					val = ko.utils.unwrapObservable(obj);

				if (obj.__kv_traversed === true) { return; }
				
				if (options.deep) {
				    obj.__kv_traversed = true;
				    flagged.push(obj);
				}

				//default level value depends on deep option.
				level = (level !== undefined ? level : options.deep ? 1 : -1);

				// if object is observable then add it to the list
				if (ko.isObservable(obj)) {

					//make sure it is validatable object
					if (!obj.isValid) { obj.extend({ validatable: true }); }
					validatablesTemp.push(obj);
				}

				//get list of values either from array or object but ignore non-objects
				if (val) {
					if (utils.isArray(val)) {
						objValues = val;
					} else if (utils.isObject(val)) {
						objValues = utils.values(val);
					}
				}

				//process recurisvely if it is deep grouping
				if (level !== 0) {
					ko.utils.arrayForEach(objValues, function (observable) {

						//but not falsy things and not HTML Elements
						if (observable && !observable.nodeType) { traverse(observable, level + 1); }
					});
				}
			};

			//if using observables then traverse structure once and add observables
			if (options.observable) {

				traverse(obj);
				dispose();
				
				validatables(validatablesTemp);	//update validatables

				result = ko.computed(function () {
					var errors = [];
					ko.utils.arrayForEach(validatables(), function (observable) {
						if (!observable.isValid()) {
							errors.push(observable.error);
						}
					});
					return errors;
				});

			} else { //if not using observables then every call to error() should traverse the structure
				result = function () {
					var errors = [];
					validatablesTemp = []; //clear validatablesTemp
					traverse(obj); // and traverse tree again
					dispose();

					validatables(validatablesTemp);	//update validatables

					ko.utils.arrayForEach(validatables(), function (observable) {
						if (!observable.isValid()) {
							errors.push(observable.error);
						}
					});
					return errors;
				};
			}

			result.showAllMessages = function (show) { // thanks @heliosPortal
				if (show === undefined) {//default to true
					show = true;
				}

				// ensure we have latest changes
				result();

				ko.utils.arrayForEach(validatables(), function (observable) {
					observable.isModified(show);
				});
			};

			obj.errors = result;
			obj.isValid = function () {
				return obj.errors().length === 0;
			};
			obj.isAnyMessageShown = function () {
				var invalidAndModifiedPresent = false;

				// ensure we have latest changes
				result();

				ko.utils.arrayForEach(validatables(), function (observable) {
					if (!observable.isValid() && observable.isModified()) {
						invalidAndModifiedPresent = true;
					}
				});
				return invalidAndModifiedPresent;
			};

			return result;
		},

		formatMessage: function (message, params) {
			if (typeof (message) === 'function') {
				return message(params);
			}
			return message.replace(/\{0\}/gi, ko.utils.unwrapObservable(params));
		},

		// addRule:
		// This takes in a ko.observable and a Rule Context - which is just a rule name and params to supply to the validator
		// ie: ko.validation.addRule(myObservable, {
		//          rule: 'required',
		//          params: true
		//      });
		//
		addRule: function (observable, rule) {
			observable.extend({ validatable: true });

			//push a Rule Context to the observables local array of Rule Contexts
			observable.rules.push(rule);
			return observable;
		},

		// addAnonymousRule:
		// Anonymous Rules essentially have all the properties of a Rule, but are only specific for a certain property
		// and developers typically are wanting to add them on the fly or not register a rule with the 'ko.validation.rules' object
		//
		// Example:
		// var test = ko.observable('something').extend{(
		//      validation: {
		//          validator: function(val, someOtherVal){
		//              return true;
		//          },
		//          message: "Something must be really wrong!',
		//          params: true
		//      }
		//  )};
		addAnonymousRule: function (observable, ruleObj) {
			var ruleName = utils.newId();

			if (ruleObj['message'] === undefined) {
				ruleObj['message'] = 'Error';
			}

			//Create an anonymous rule to reference
			ko.validation.rules[ruleName] = ruleObj;

			//add the anonymous rule to the observable
			ko.validation.addRule(observable, {
				rule: ruleName,
				params: ruleObj.params
			});
		},

		addExtender: function (ruleName) {
			ko.extenders[ruleName] = function (observable, params) {
				//params can come in a few flavors
				// 1. Just the params to be passed to the validator
				// 2. An object containing the Message to be used and the Params to pass to the validator
				// 3. A condition when the validation rule to be applied
				//
				// Example:
				// var test = ko.observable(3).extend({
				//      max: {
				//          message: 'This special field has a Max of {0}',
				//          params: 2,
				//          onlyIf: function() {
				//                      return specialField.IsVisible();
				//                  }
				//      }
				//  )};
				//
				if (params.message || params.onlyIf) { //if it has a message or condition object, then its an object literal to use
					return ko.validation.addRule(observable, {
						rule: ruleName,
						message: params.message,
						params: utils.isEmptyVal(params.params) ? true : params.params,
						condition: params.onlyIf
					});
				} else {
					return ko.validation.addRule(observable, {
						rule: ruleName,
						params: params
					});
				}
			};
		},

		// loops through all ko.validation.rules and adds them as extenders to
		// ko.extenders
		registerExtenders: function () { // root extenders optional, use 'validation' extender if would cause conflicts
			if (configuration.registerExtenders) {
				for (var ruleName in ko.validation.rules) {
					if (ko.validation.rules.hasOwnProperty(ruleName)) {
						if (!ko.extenders[ruleName]) {
							ko.validation.addExtender(ruleName);
						}
					}
				}
			}
		},

		//creates a span next to the @element with the specified error class
		insertValidationMessage: function (element) {
			var span = document.createElement('SPAN');
			span.className = utils.getConfigOptions(element).errorMessageClass;
			utils.insertAfter(element, span);
			return span;
		},

		// if html-5 validation attributes have been specified, this parses
		// the attributes on @element
		parseInputValidationAttributes: function (element, valueAccessor) {
			ko.utils.arrayForEach(ko.validation.configuration.html5Attributes, function (attr) {
				if (utils.hasAttribute(element, attr)) {
					ko.validation.addRule(valueAccessor(), {
						rule: attr,
						params: element.getAttribute(attr) || true
					});
				}
			});

			var currentType = element.getAttribute('type');
			ko.utils.arrayForEach(ko.validation.configuration.html5InputTypes, function (type) {
				if (type === currentType) {
					ko.validation.addRule(valueAccessor(), {
						rule: (type === 'date') ? 'dateISO' : type,
						params: true
					});
				}
			});
		},

		// writes html5 validation attributes on the element passed in
		writeInputValidationAttributes: function (element, valueAccessor) {
			var observable = valueAccessor();

			if (!observable || !observable.rules) {
				return;
			}

			var contexts = observable.rules(); // observable array

			// loop through the attributes and add the information needed
			ko.utils.arrayForEach(ko.validation.configuration.html5Attributes, function (attr) {
				var params;
				var ctx = ko.utils.arrayFirst(contexts, function (ctx) {
					return ctx.rule.toLowerCase() === attr.toLowerCase();
				});

				if (!ctx) {
					return;
				}

				params = ctx.params;

				// we have to do some special things for the pattern validation
				if (ctx.rule === "pattern") {
					if (ctx.params instanceof RegExp) {
						params = ctx.params.source; // we need the pure string representation of the RegExpr without the //gi stuff
					}
				}

				// we have a rule matching a validation attribute at this point
				// so lets add it to the element along with the params
				element.setAttribute(attr, params);
			});

			contexts = null;
		},

		//take an existing binding handler and make it cause automatic validations
		makeBindingHandlerValidatable: function (handlerName) {
			var init = ko.bindingHandlers[handlerName].init;

			ko.bindingHandlers[handlerName].init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

				init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);

				return ko.bindingHandlers['validationCore'].init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
			};
		}
	};
}());

// expose api publicly
ko.utils.extend(ko.validation, api);;//Validation Rules:
// You can view and override messages or rules via:
// ko.validation.rules[ruleName]
//
// To implement a custom Rule, simply use this template:
// ko.validation.rules['<custom rule name>'] = {
//      validator: function (val, param) {
//          <custom logic>
//          return <true or false>;
//      },
//      message: '<custom validation message>' //optionally you can also use a '{0}' to denote a placeholder that will be replaced with your 'param'
// };
//
// Example:
// ko.validation.rules['mustEqual'] = {
//      validator: function( val, mustEqualVal ){
//          return val === mustEqualVal;
//      },
//      message: 'This field must equal {0}'
// };
//
ko.validation.rules = {};
ko.validation.rules['required'] = {
	validator: function (val, required) {
		var stringTrimRegEx = /^\s+|\s+$/g,
			testVal;

		if (val === undefined || val === null) {
			return !required;
		}

		testVal = val;
		if (typeof (val) === "string") {
			testVal = val.replace(stringTrimRegEx, '');
		}

		if (!required) {// if they passed: { required: false }, then don't require this
			return true;
		}

		return ((testVal + '').length > 0);
	},
	message: 'This field is required.'
};

ko.validation.rules['min'] = {
	validator: function (val, min) {
		return ko.validation.utils.isEmptyVal(val) || val >= min;
	},
	message: 'Please enter a value greater than or equal to {0}.'
};

ko.validation.rules['max'] = {
	validator: function (val, max) {
		return ko.validation.utils.isEmptyVal(val) || val <= max;
	},
	message: 'Please enter a value less than or equal to {0}.'
};

ko.validation.rules['minLength'] = {
	validator: function (val, minLength) {
		return ko.validation.utils.isEmptyVal(val) || val.length >= minLength;
	},
	message: 'Please enter at least {0} characters.'
};

ko.validation.rules['maxLength'] = {
	validator: function (val, maxLength) {
		return ko.validation.utils.isEmptyVal(val) || val.length <= maxLength;
	},
	message: 'Please enter no more than {0} characters.'
};

ko.validation.rules['pattern'] = {
	validator: function (val, regex) {
		return ko.validation.utils.isEmptyVal(val) || val.toString().match(regex) !== null;
	},
	message: 'Please check this value.'
};

ko.validation.rules['step'] = {
	validator: function (val, step) {

		// in order to handle steps of .1 & .01 etc.. Modulus won't work
		// if the value is a decimal, so we have to correct for that
		if (ko.validation.utils.isEmptyVal(val) || step === 'any') { return true; }
		var dif = (val * 100) % (step * 100);
		return Math.abs(dif) < 0.00001 || Math.abs(1 - dif) < 0.00001;
	},
	message: 'The value must increment by {0}'
};

ko.validation.rules['email'] = {
	validator: function (val, validate) {
		if (!validate) { return true; }

		//I think an empty email address is also a valid entry
		//if one want's to enforce entry it should be done with 'required: true'
		return ko.validation.utils.isEmptyVal(val) || (
			// jquery validate regex - thanks Scott Gonzalez
			validate && /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(val)
		);
	},
	message: 'Please enter a proper email address'
};

ko.validation.rules['date'] = {
	validator: function (value, validate) {
		if (!validate) { return true; }
		return ko.validation.utils.isEmptyVal(value) || (validate && !/Invalid|NaN/.test(new Date(value)));
	},
	message: 'Please enter a proper date'
};

ko.validation.rules['dateISO'] = {
	validator: function (value, validate) {
		if (!validate) { return true; }
		return ko.validation.utils.isEmptyVal(value) || (validate && /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value));
	},
	message: 'Please enter a proper date'
};

ko.validation.rules['number'] = {
	validator: function (value, validate) {
		if (!validate) { return true; }
		return ko.validation.utils.isEmptyVal(value) || (validate && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value));
	},
	message: 'Please enter a number'
};

ko.validation.rules['digit'] = {
	validator: function (value, validate) {
		if (!validate) { return true; }
		return ko.validation.utils.isEmptyVal(value) || (validate && /^\d+$/.test(value));
	},
	message: 'Please enter a digit'
};

ko.validation.rules['phoneUS'] = {
	validator: function (phoneNumber, validate) {
		if (!validate) { return true; }
		if (ko.validation.utils.isEmptyVal(phoneNumber)) { return true; } // makes it optional, use 'required' rule if it should be required
		if (typeof (phoneNumber) !== 'string') { return false; }
		phoneNumber = phoneNumber.replace(/\s+/g, "");
		return validate && phoneNumber.length > 9 && phoneNumber.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
	},
	message: 'Please specify a valid phone number'
};

ko.validation.rules['equal'] = {
	validator: function (val, params) {
		var otherValue = params;
		return val === ko.validation.utils.getValue(otherValue);
	},
	message: 'Values must equal'
};

ko.validation.rules['notEqual'] = {
	validator: function (val, params) {
		var otherValue = params;
		return val !== ko.validation.utils.getValue(otherValue);
	},
	message: 'Please choose another value.'
};

//unique in collection
// options are:
//    collection: array or function returning (observable) array
//              in which the value has to be unique
//    valueAccessor: function that returns value from an object stored in collection
//              if it is null the value is compared directly
//    external: set to true when object you are validating is automatically updating collection
ko.validation.rules['unique'] = {
	validator: function (val, options) {
		var c = ko.validation.utils.getValue(options.collection),
			external = ko.validation.utils.getValue(options.externalValue),
			counter = 0;

		if (!val || !c) { return true; }

		ko.utils.arrayFilter(ko.utils.unwrapObservable(c), function (item) {
			if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
		});
		// if value is external even 1 same value in collection means the value is not unique
		return counter < (external !== undefined && val !== external ? 1 : 2);
	},
	message: 'Please make sure the value is unique.'
};


//now register all of these!
(function () {
	ko.validation.registerExtenders();
}());
;// The core binding handler
// this allows us to setup any value binding that internally always
// performs the same functionality
ko.bindingHandlers['validationCore'] = (function () {

	return {
		init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
			var config = ko.validation.utils.getConfigOptions(element);

			// parse html5 input validation attributes, optional feature
			if (config.parseInputAttributes) {
				ko.validation.utils.async(function () { ko.validation.parseInputValidationAttributes(element, valueAccessor); });
			}

			// if requested insert message element and apply bindings
			if (config.insertMessages && ko.validation.utils.isValidatable(valueAccessor())) {

				// insert the <span></span>
				var validationMessageElement = ko.validation.insertValidationMessage(element);

				// if we're told to use a template, make sure that gets rendered
				if (config.messageTemplate) {
					ko.renderTemplate(config.messageTemplate, { field: valueAccessor() }, null, validationMessageElement, 'replaceNode');
				} else {
					ko.applyBindingsToNode(validationMessageElement, { validationMessage: valueAccessor() });
				}
			}

			// write the html5 attributes if indicated by the config
			if (config.writeInputAttributes && ko.validation.utils.isValidatable(valueAccessor())) {

				ko.validation.writeInputValidationAttributes(element, valueAccessor);
			}

			// if requested, add binding to decorate element
			if (config.decorateElement && ko.validation.utils.isValidatable(valueAccessor())) {
				ko.applyBindingsToNode(element, { validationElement: valueAccessor() });
			}
		},

		update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
			// hook for future extensibility
		}
	};

}());

// override for KO's default 'value' and 'checked' bindings
ko.validation.makeBindingHandlerValidatable("value");
ko.validation.makeBindingHandlerValidatable("checked");


ko.bindingHandlers['validationMessage'] = { // individual error message, if modified or post binding
	update: function (element, valueAccessor) {
		var obsv = valueAccessor(),
			config = ko.validation.utils.getConfigOptions(element),
			val = ko.utils.unwrapObservable(obsv),
			msg = null,
			isModified = false,
			isValid = false;

		obsv.extend({ validatable: true });

		isModified = obsv.isModified();
		isValid = obsv.isValid();

		// create a handler to correctly return an error message
		var errorMsgAccessor = function () {
			if (!config.messagesOnModified || isModified) {
				return isValid ? null : obsv.error;
			} else {
				return null;
			}
		};

		//toggle visibility on validation messages when validation hasn't been evaluated, or when the object isValid
		var visiblityAccessor = function () {
			return (!config.messagesOnModified || isModified) ? !isValid : false;
		};

		ko.bindingHandlers.text.update(element, errorMsgAccessor);
		ko.bindingHandlers.visible.update(element, visiblityAccessor);
	}
};

ko.bindingHandlers['validationElement'] = {
	update: function (element, valueAccessor) {
		var obsv = valueAccessor(),
			config = ko.validation.utils.getConfigOptions(element),
			val = ko.utils.unwrapObservable(obsv),
			msg = null,
			isModified = false,
			isValid = false;

		obsv.extend({ validatable: true });

		isModified = obsv.isModified();
		isValid = obsv.isValid();

		// create an evaluator function that will return something like:
		// css: { validationElement: true }
		var cssSettingsAccessor = function () {
			var css = {};

			var shouldShow = ((!config.decorateElementOnModified || isModified) ? !isValid : false);

			if (!config.decorateElement) { shouldShow = false; }

			// css: { validationElement: false }
			css[config.errorElementClass] = shouldShow;

			return css;
		};

		//add or remove class on the element;
		ko.bindingHandlers.css.update(element, cssSettingsAccessor);
		if (!config.errorsAsTitle) { return; }

		var origTitle = ko.validation.utils.getAttribute(element, 'data-orig-title'),
			elementTitle = element.title,
			titleIsErrorMsg = ko.validation.utils.getAttribute(element, 'data-orig-title') === "true";

		var errorMsgTitleAccessor = function () {
			if (!config.errorsAsTitleOnModified || isModified) {
				if (!isValid) {
					return { title: obsv.error, 'data-orig-title': ko.validation.utils.getOriginalElementTitle(element) };
				} else {
					return { title: ko.validation.utils.getOriginalElementTitle(element), 'data-orig-title': null };
				}
			}
		};
		ko.bindingHandlers.attr.update(element, errorMsgTitleAccessor);
	}
};

// ValidationOptions:
// This binding handler allows you to override the initial config by setting any of the options for a specific element or context of elements
//
// Example:
// <div data-bind="validationOptions: { insertMessages: true, messageTemplate: 'customTemplate', errorMessageClass: 'mySpecialClass'}">
//      <input type="text" data-bind="value: someValue"/>
//      <input type="text" data-bind="value: someValue2"/>
// </div>
ko.bindingHandlers['validationOptions'] = (function () {
	return {
		init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
			var options = ko.utils.unwrapObservable(valueAccessor());
			if (options) {
				var newConfig = ko.utils.extend({}, ko.validation.configuration);
				ko.utils.extend(newConfig, options);

				//store the validation options on the node so we can retrieve it later
				ko.validation.utils.setDomData(element, newConfig);
			}
		}
	};
}());
;// Validation Extender:
// This is for creating custom validation logic on the fly
// Example:
// var test = ko.observable('something').extend{(
//      validation: {
//          validator: function(val, someOtherVal){
//              return true;
//          },
//          message: "Something must be really wrong!',
//          params: true
//      }
//  )};
ko.extenders['validation'] = function (observable, rules) { // allow single rule or array
	ko.utils.arrayForEach(ko.validation.utils.isArray(rules) ? rules : [rules], function (rule) {
		// the 'rule' being passed in here has no name to identify a core Rule,
		// so we add it as an anonymous rule
		// If the developer is wanting to use a core Rule, but use a different message see the 'addExtender' logic for examples
		ko.validation.addAnonymousRule(observable, rule);
	});
	return observable;
};

//This is the extender that makes a Knockout Observable also 'Validatable'
//examples include:
// 1. var test = ko.observable('something').extend({validatable: true});
// this will ensure that the Observable object is setup properly to respond to rules
//
// 2. test.extend({validatable: false});
// this will remove the validation properties from the Observable object should you need to do that.
ko.extenders['validatable'] = function (observable, enable) {
	if (enable && !ko.validation.utils.isValidatable(observable)) {

		observable.error = ko.observable(null); // holds the error message, we only need one since we stop processing validators when one is invalid

		// observable.rules:
		// ObservableArray of Rule Contexts, where a Rule Context is simply the name of a rule and the params to supply to it
		//
		// Rule Context = { rule: '<rule name>', params: '<passed in params>', message: '<Override of default Message>' }
		observable.rules = ko.observableArray(); //holds the rule Contexts to use as part of validation

		//in case async validation is occuring
		observable.isValidating = ko.observable(false);

		//the true holder of whether the observable is valid or not
		observable.__valid__ = ko.observable(true);

		observable.isModified = ko.observable(false);

		// we use a computed here to ensure that anytime a dependency changes, the
		// validation logic evaluates
		var h_obsValidationTrigger = ko.computed(function () {
			var obs = observable(),
				ruleContexts = observable.rules();

			ko.validation.validateObservable(observable);

			return true;
		});

		// a semi-protected observable
		observable.isValid = ko.computed(function () {
			return observable.__valid__();
		});

		//manually set error state
		observable.setError = function (error) {
			observable.error(error);
			observable.__valid__(false);
		};

		//manually clear error state
		observable.clearError = function () {
			observable.error(null);
			observable.__valid__(true);
		};

		//subscribe to changes in the observable
		var h_change = observable.subscribe(function () {
			observable.isModified(true);
		});

		observable._disposeValidation = function () {
			//first dispose of the subscriptions
			observable.isValid.dispose();
			observable.rules.removeAll();
			observable.isModified._subscriptions['change'] = [];
			observable.isValidating._subscriptions['change'] = [];
			observable.__valid__._subscriptions['change'] = [];
			h_change.dispose();
			h_obsValidationTrigger.dispose();

			delete observable['rules'];
			delete observable['error'];
			delete observable['isValid'];
			delete observable['isValidating'];
			delete observable['__valid__'];
			delete observable['isModified'];
		};
	} else if (enable === false && ko.validation.utils.isValidatable(observable)) {

		if (observable._disposeValidation) {
			observable._disposeValidation();
		}
	}
	return observable;
};

function validateSync(observable, rule, ctx) {
	//Execute the validator and see if its valid
	if (!rule.validator(observable(), ctx.params === undefined ? true : ctx.params)) { // default param is true, eg. required = true

		//not valid, so format the error message and stick it in the 'error' variable
		observable.error(ko.validation.formatMessage(ctx.message || rule.message, ctx.params));
		observable.__valid__(false);
		return false;
	} else {
		return true;
	}
}

function validateAsync(observable, rule, ctx) {
	observable.isValidating(true);

	var callBack = function (valObj) {
		var isValid = false,
			msg = '';

		if (!observable.__valid__()) {

			// since we're returning early, make sure we turn this off
			observable.isValidating(false);

			return; //if its already NOT valid, don't add to that
		}

		//we were handed back a complex object
		if (valObj['message']) {
			isValid = valObj.isValid;
			msg = valObj.message;
		} else {
			isValid = valObj;
		}

		if (!isValid) {
			//not valid, so format the error message and stick it in the 'error' variable
			observable.error(ko.validation.formatMessage(msg || ctx.message || rule.message, ctx.params));
			observable.__valid__(isValid);
		}

		// tell it that we're done
		observable.isValidating(false);
	};

	//fire the validator and hand it the callback
	rule.validator(observable(), ctx.params || true, callBack);
}

ko.validation.validateObservable = function (observable) {
	var i = 0,
		rule, // the rule validator to execute
		ctx, // the current Rule Context for the loop
		ruleContexts = observable.rules(), //cache for iterator
		len = ruleContexts.length; //cache for iterator

	for (; i < len; i++) {

		//get the Rule Context info to give to the core Rule
		ctx = ruleContexts[i];

		// checks an 'onlyIf' condition
		if (ctx.condition && !ctx.condition()) {
			continue;
		}

		//get the core Rule to use for validation
		rule = ko.validation.rules[ctx.rule];

		if (rule['async'] || ctx['async']) {
			//run async validation
			validateAsync(observable, rule, ctx);

		} else {
			//run normal sync validation
			if (!validateSync(observable, rule, ctx)) {
				return false; //break out of the loop
			}
		}
	}
	//finally if we got this far, make the observable valid again!
	observable.error(null);
	observable.__valid__(true);
	return true;
};;
//quick function to override rule messages
ko.validation.localize = function (msgTranslations) {

	var msg, rule;

	//loop the properties in the object and assign the msg to the rule
	for (rule in msgTranslations) {
		if (ko.validation.rules.hasOwnProperty(rule)) {
			ko.validation.rules[rule].message = msgTranslations[rule];
		}
	}
};;ko.applyBindingsWithValidation = function (viewModel, rootNode, options) {
	var len = arguments.length,
		node, config;

	if (len > 2) { // all parameters were passed
		node = rootNode;
		config = options;
	} else if (len < 2) {
		node = document.body;
	} else { //have to figure out if they passed in a root node or options
		if (arguments[1].nodeType) { //its a node
			node = rootNode;
		} else {
			config = arguments[1];
		}
	}

	ko.validation.init();

	if (config) { ko.validation.utils.setDomData(node, config); }

	ko.applyBindings(viewModel, rootNode);
};

//override the original applyBindings so that we can ensure all new rules and what not are correctly registered
var origApplyBindings = ko.applyBindings;
ko.applyBindings = function (viewModel, rootNode) {

	ko.validation.init();

	origApplyBindings(viewModel, rootNode);
};

ko.validatedObservable = function (initialValue) {
	if (!ko.validation.utils.isObject(initialValue)) { return ko.observable(initialValue).extend({ validatable: true }); }

	var obsv = ko.observable(initialValue);
	obsv.errors = ko.validation.group(initialValue);
	obsv.isValid = ko.computed(function () {
		return obsv.errors().length === 0;
	});

	return obsv;
};;}));





//////////////////////////////////////////////////////////////////////////





var Author = function(form, initial, name, affiliations){
    var self = this;
//    self.form = form;                         // これをすると依存関係の検出時に無限ループに落ち込む
//    self.form = function(){ return form; }    // これならOK
    self.initial = ko.observable(initial).extend({ 
                    required: true,
                    pattern: {
                        message: 'First and middle names should be typed as initial(s) '+
                                 'that is expected to have a period character "." at the end.',
                        params: '\\.$'
                    }
                });
    self.name = ko.observable(name).extend({ required: true });
    self.affiliations = ko.observable(affiliations).extend({ 
                    required: true,
                    pattern: {
                        message: 'Affiliation must be specified by comma separated numbers.',
                        params: '^ *[1-9][0-9]*( *, *[1-9][0-9]*)* *$'
                    },
                    validation: [ {
                        validator: function(val) {
                            var ok = true;
                            _.each(val.split(/ *, */), function(a) {
                                if(Number(a) > form.affiliations().length ||
                                    (Number(a) == 0) )
                                    ok = false;
                            });
                            return ok;
                        },
                        message: 'Specified affiliation number does not exist.'
                    }, {
                        validator: function(val) {
                            var as = _.sortBy(
                                        _.map( val.split(/ *, */), 
                                               function(a) { return Number(a); }), 
                                        function(a) {return a});
                            for(var i=0; i<as.length-1; i++)
                                if(as[i]==as[i+1])
                                    return false;
                            return true;
                        },
                        message: 'Duplicated affiliation number exist.'
                    }]
                });
    self.errors = ko.validation.group(self, { deep: true, observable: false });
};

var Affiliation = function(institute, country) {
    var self = this;

    self.institute = ko.observable(institute).extend({ required: true});
    self.country   = ko.observable(country).extend({ required: true});
    self.errors = ko.validation.group(self, { deep: true, observable: false });
};

var TopicList

var AbstractForm = function(abstract) {
    var self = this;
    if(!abstract) {
        abstract = { id: 0, title: '', presenter: '0', stylePrefered: 'Whichever', 
                     topic1index: 0, topic2index: 0,
                     authors: [{}, {}], affiliations: [{}],
                     applyPosterAward: false, dateOfBirth:undefined, recommendation:'' };
    }

    self.id             = abstract.id;
    self.title          = ko.observable(abstract.title).extend({
                                required: true
                            });
    self.presenter      = ko.observable(abstract.presenter).extend({
                                required: true
                            });
    self.stylePrefered  = ko.observable(abstract.stylePrefered);
    
    self.topics         = [
                    "-- select one --",
                    "Bio- & macromolecules and polymers",
                    "Semiconductor and metal surface structure",
                    "Adsorbates and reactions on semiconductor and metal surfaces",
                    "Magnetic systems",
                    "Nanotubes and 1-D wires",
                    "Catalysis & electrochemistry",
                    "Nano-optical phenomena",
                    "Interaction within and between single molecules",
                    "Atom and molecular manipulation",
                    "Nano-patterning",
                    "Oxides",
                    "Cells and biological systems",
                    "Theory of probe-matter interactions",
                    "Tip preparation and functionalisation",
                    "Bottom-up process",
                    "Molecular electronics "
                ];
    self.topic1         = ko.observable(self.topics[0]).extend({
                            validation: {
                                validator: function (val) {
                                    return _.indexOf(self.topics, val) >= 1;
                                },
                                message: 'Select a topic.',
                                params: undefined
                            }                            
                        });
    self.topic2         = ko.observable(self.topics[0]);
    self.topic1index    = ko.computed({
                            read: function() { return _.indexOf(self.topics, self.topic1()); },
                            write: function(v) { self.topic1(self.topics[v]); }
                         });
    self.topic1index(abstract.topic1index);
    self.topic2index    = ko.computed({
                            read: function() { return _.indexOf(self.topics, self.topic2()); },
                            write: function(v) { self.topic2(self.topics[v]); }
                          });
    self.topic2index(abstract.topic2index);

    self.affiliations   = ko.observableArray(
                    ko.utils.arrayMap(abstract.affiliations, function(entry){
                        return new Affiliation(entry.institute, entry.country);})
                );
    self.affiliationAdd = function() {
                    self.affiliations.push(new Affiliation())
                }
    self.affiliationRemove = function(affiliation) { 
                    if(self.affiliations().length>1)
                        self.affiliations.remove(affiliation) 
                };
    self.affiliationPlaceHolder = function(index) {
                    return index==0 ? ['IBM Zurich Research Laboratory','Switzerland'] : ['',''];
                };

    self.authors        = ko.observableArray(
                    ko.utils.arrayMap(abstract.authors, function(entry){
                            return new Author(self, entry.initial, entry.name, entry.affiliations);})
                );
    self.authorAdd      = function() {
                    self.authors.push(new Author(self))
                };
    self.authorRemove   = function(author) { 
                    if(self.authors().length>1)
                        self.authors.remove(author);
                };
    self.authorUp       = function(author) { 
                    for(var i=0; i<self.authors().length; i++)
                        self.authors()[i].newIndex = i;
                    i = self.authors().indexOf(author);
                    self.authors()[i].newIndex = i-1;
                    self.authors()[i-1].newIndex = i;
                    self.authors.sort(function(left, right) { 
                        return left.newIndex == right.newIndex ? 0 : (left.newIndex < right.newIndex ? -1 : 1) ;
                    });
                    self.presenter.valueHasMutated();   // なぜこれが必要か?
                };
    self.authorDown     = function(author) { 
                    for(var i=0; i<self.authors().length; i++)
                        self.authors()[i].newIndex = i;
                    i = self.authors().indexOf(author);
                    self.authors()[i].newIndex = i+1;
                    self.authors()[i+1].newIndex = i;
                    self.authors.sort(function(left, right) { 
                        return left.newIndex == right.newIndex ? 0 : (left.newIndex < right.newIndex ? -1 : 1) ;
                    });
                    self.presenter.valueHasMutated();   // なぜこれが必要か?
                };
    self.authorPlaceHolder 
                        = function(index) {
                    return index>1 ? ['','',''] : [['G.', 'Binnig', '1'], ['H.', 'Rohrer', '1']][index];
                };

    self.applyPosterAward   = ko.observable(abstract.applyPosterAward);
    self.dateOfBirth        = ko.observable(abstract.dateOfBirth).extend({
                    required: {
                        onlyIf: function () { 
                          return self.applyPosterAward();
                        }
                    },
                    pattern: {
                        params: '^(20[01][0-9]|19[0-9][0-9])-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$',
                        message: 'Input the date in a form "yyyy-mm-dd".'
                    }
                });
    self.recommendation     = ko.observable(abstract.recommendation).extend({
                    required: {
                        onlyIf: function () { 
                          return self.applyPosterAward();
                        }
                    },
                    validation: {
                        validator: function(val) {
                            return val.split(/[ \n\t]+/).length < 210;
                        },
                        message: "Too long."
                    }
                });
                
    self.previewErrors = ko.validation.group(
        [self.authors, self.affiliations, self.title], { deep: true, observable: false });

    self.errors = ko.validation.group(self, { deep: true, observable: false });
    
    self.onSave = function() {
        var js = ko.toJS(self);
        js.topics = undefined;
        js.topic1 = undefined;
        js.topic2 = undefined;
        alert(ko.toJSON(js));
    };
    
    self.onSubmit = function(formElement) {
        if(self.errors().length > 0) {
            self.errors.showAllMessages();
            alert("There are some errors. Please correct them before submission.");
            return false;
        }
        return true;
    };
}

abstract = null;

ko.validation.init( {
    decorateElement: true, 
    errorElementClass: 'validationError',
    messageTemplate: 'validationErrorMessage'
} );

ko.applyBindings(new AbstractForm(abstract));

External resources loaded into this fiddle:

<script type="text/html" id="validationErrorMessage">
    <p data-bind="visible: field.isModified() && !field.isValid(), 
                  text: field.error" class="validationMessage"></p>
</script>

<h1>Abstract Submission Form</h1>
<form id='AbstractForm' data-bind="submit: onSubmit">

    <section>
        <h3>Registration-Id:</h3>
        <!--ko text: id --><!--/ko-->
        <input type="hidden" name="id" data-bind="value: id">
    </section>

    <section>
        <h3>Presentation Title:</h3>
        <textarea data-bind="value: title" name="title"></textarea>
    </section>

    <section>
        <h3>Author List:</h3>
        <table id="authors">
            <tr><th>Presenter</th><th>Initial</th><th>Name</th><th>Affiliations</th></tr>
            <!--ko foreach: authors-->
                <tr data-bind='validationOptions: { insertMessages: false }'>
                    <td> &nbsp; <!--ko text: $index() + 1 + '.' --><!--/ko--> &nbsp; 
                        <input type="radio" name="presenter" data-bind="checked: $parent.presenter, value: $index()"></td>
                    <td><input data-bind="value: initial, attr: {name:'author['+$index()+'].initial', placeholder:  $parent.authorPlaceHolder($index())[0]}"></td>
                    <td><input data-bind="value: name, attr:{name:'author['+$index()+'].name', placeholder:  $parent.authorPlaceHolder($index())[1]}"></td>
                    <td><input data-bind="value: affiliations, attr:{name:'author['+$index()+'].affiliations', placeholder:  $parent.authorPlaceHolder($index())[2]}"></td>
                    <td>
                        <a href="" data-bind="click: $parent.authorRemove, if:$parent.authors().length>1">remove</a>
                        <a href="" data-bind="click: $parent.authorUp, if:$index()>0">up</a>
                        <a href="" data-bind="click: $parent.authorDown, if:$index()<$parent.authors().length-1">down</a>
                    </td>
                </tr>
                <tr data-bind="visible: errors().length>0">
                    <td>
                    <td colspan="3" class="validationMessage" style="width:310px">
                        <p data-bind="validationMessage: initial" fieldName="Initial"></p>
                        <p data-bind="validationMessage: name" fieldName="Name"></p>
                        <p data-bind="validationMessage: affiliations" fieldName="Affiliations"></p>
                </tr>
            <!--/ko-->
        </table>
        <div style="padding:5px 0 0 20px;"><a href="" data-bind="click: authorAdd">add a new line</a></div>
        </div>
    </section>

    <section>
        <h3>Affiliation List:</h3>
        <table id="affiliations">
            <tr><th><th>Institute</th><th>Country</th></tr>
            <!--ko foreach: affiliations-->
                <tr data-bind='validationOptions: { insertMessages: false }'>
                    <td data-bind="text: $index() + 1 + '.'"></td>
                    <td><input data-bind="value: institute, attr:{name:'affiliation['+$index()+'].institute', placeholder: $parent.affiliationPlaceHolder($index())[0] }"></td>
                    <td><input data-bind="value: country, attr:{name:'affiliation['+$index()+'].country', placeholder: $parent.affiliationPlaceHolder($index())[1] }"></td>
                    <td><a href="" data-bind="click: $parent.affiliationRemove, if: $parent.affiliations().length>1">remove</a></td>
                </tr>
                <tr data-bind="visible: errors().length>0">
                    <td>
                    <td colspan="2" class="validationMessage" style="width:400px">
                        <p data-bind="validationMessage: institute" fieldName="Institute"></p>
                        <p data-bind="validationMessage: country" fieldName="Country"></p>
                </tr>
            <!--/ko-->
        </table>
        <div style="padding:5px 0 0 20px;"><a href="" data-bind="click: affiliationAdd">add a new line</a></div>
    </section>

    <section id="preview">
        <h3>Preview:</h3>
        <!--ko if: errors().length==0 -->
            <div class="title" data-bind="html: title()==='' ? '<span class=\'alert\'>-- title is missing --</span>' : title()"></div>
            <div class="authors" data-bind="foreach: authors">
                <span data-bind="css: $index()==$parent.presenter() ? 'author presenter' : 'author'"><!--ko text: initial--><!--/ko--> 
                <!--ko text: name--><!--/ko--><span class="affiliations" 
                data-bind="text: affiliations"></span></span><!--ko text: 
                $index()===($parent.authors().length-2) ? ' and ' : 
                $index()===($parent.authors().length-1) ? '' : ', ' --><!--/ko-->
            </div>
            <div class="affiliations" data-bind="foreach: affiliations">
                <span class="affiliation"><span class="number" data-bind="text: $index()+1"></span><!--ko text: institute--><!--/ko-->, 
                <!--ko text: country--><!--/ko--></span><!--ko text: $index()===($parent.affiliations().length-1) ? '' : ', ' --><!--/ko-->
            </div>
        <!--/ko-->
        <!--ko if: errors().length>0 -->
            <p>There are some errors in the fields.</p>
        <!--/ko-->
    </section>

    <section>
        <h3>Preferential Presentation Style:</h3>
        <div>
        <label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Oral">Oral</label>
        <label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Poster">Poster</label>
        <label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Whichever">Whichever</label>
        &nbsp; &nbsp; (
        <label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Invited">Invited</label>
        <label><input type="radio" name="stylePrefered" data-bind="checked: stylePrefered" value="Plenary">Plenary</label>
        )
        </p>
        <span data-bind="if: stylePrefered()=='Invited' || stylePrefered()=='Plenary'">
            <br>
            * Select 'Invited' or 'Plenary' only if you have received an invitation letter in advance.
        </span>
    </section>

    <section>
        <h3>Topics:</h3>
        <p>Topic1:</p>
        <div><select data-bind="options: topics, value: topic1"></select>
        <input type="hidden" name="topic1" data-bind="value: topic1index"></div>
        <br>
        <p>Topic2 (optional):</p>
        <div><select data-bind="options: topics, value: topic2"></select>
        <input type="hidden" name="topic2" data-bind="value: topic2index"></div>
    </section>

    <section id="PosterAward">
        <h3>Poster Award:</h3>
        <label><input name="applyPosterAward" type="checkbox" data-bind="checked: applyPosterAward">
        Apply for Poster Award</label>
        <p>(You must be younger than or equal to 35 years old on 2013-01-01)</p>
        <br>
        <div data-bind="css: applyPosterAward() ? '' : 'disable'">
            Date of Birth:<br>
            <div>&nbsp; <input name="dateOfBirth" placeHolder="yyyy-mm-dd" data-bind="value: dateOfBirth, enable: applyPosterAward()"></div>
            <br>
            Self recommendation of your paper (less than 200 words):<br>
            <div>&nbsp; <textarea name="recommendation" data-bind="value: recommendation, enable: applyPosterAward()"></textarea></div>
        </div>
    </section>
    <br>
    <br>
    <input type="button" data-bind="click: onSave" value="Save This Form Temporarily">
    <input type="submit" value="Submit"></input><br>
    <br>
    <br>
    <br>
    <br>
</form>
/* リセットしてしまった見出しのサイズ調整 */

h1 {
    font-size: 160%;
}

h2 {
    font-size: 130%;
}

h3 {
    font-size: 100%;
}

/* ここからが AbstractForm 関連 */

form#AbstractForm h3 {
    margin: 20px 0 10px 0;
}

form#AbstractForm textarea[name='title'] {
    width: 450px;
    height: 3em;
}

input.validationError,
select.validationError,
textarea.validationError {
    background-color: #fcc;
}

form#AbstractForm {
    line-height: 140%;
}

form#AbstractForm input,
form#AbstractForm select,
form#AbstractForm textarea {
    font-size: 100%;
}

form#AbstractForm section > :not(h3) {
    margin-left: 20px;
}

form#AbstractForm table#authors tr th,
form#AbstractForm table#authors tr td,
form#AbstractForm table#affiliations tr th,
form#AbstractForm table#affiliations tr td {
    padding: 0 5px 0 5px;
    font-weight: normal;
}

form#AbstractForm table#authors tr th,
form#AbstractForm table#affiliations tr th {
    vertical-align: top;
}

form#AbstractForm table#authors tr td:nth-child(2) input {
    width: 60px;
}

form#AbstractForm table#authors tr td:nth-child(3) input {
    width: 150px;
}

form#AbstractForm table#authors tr td:nth-child(4) input {
    width: 80px;
}

form#AbstractForm table#affiliations tr td:nth-child(2) input {
    width: 300px;
}

form#AbstractForm table#affiliations tr td:nth-child(3) input {
    width: 100px;
}

form#AbstractForm section#preview div.title {
    font-weight: bold;
}

form#AbstractForm section#preview div.authors,
form#AbstractForm section#preview div.affiliations {
    font-style: italic;
}

form#AbstractForm section#preview > div.authors > span.presenter {
    text-decoration: underline;
}

form#AbstractForm section#preview > div.authors > span.author > span.affiliations {
    vertical-align: super;
    font-size: small;
}

form#AbstractForm section#preview > div.authors > span.presenter > span.affiliations {
    text-decoration: none !important;
}

form#AbstractForm section#preview div.affiliations span.affiliation span.number {
    vertical-align: super;
    font-size: small;
}

form#AbstractForm section#PosterAward div.disable {
    color: #888;
}

form#AbstractForm section#PosterAward div textarea {
    width: 450px;
    height: 5em;
}

p[data-bind^="validationMessage:"], p.validationMessage {
    color: #f33;
}

p[data-bind^="validationMessage:"][fieldName]:before {
    content: attr(fieldName) " : "
}