-
-
Save andybooth/1009233 to your computer and use it in GitHub Desktop.
| (function () { | |
| function isArray(o) { return o.isArray || Object.prototype.toString.call(o) === '[object Array]'; } | |
| function isObject(o) { return o != null && typeof o === 'object'; } | |
| function values(o) { var r = []; for (var i in o) { r.push(o[i]); } return r; } | |
| function hasAttribute(node, attr) { if (node.hasAttribute) return (node.hasAttribute(attr)); else return (!!node.getAttribute(attr)); } | |
| function isValidatable(o) { return o.rules && o.isValid && o.isModified; } | |
| function insertAfter(node, newNode) { node.parentNode.insertBefore(newNode, node.nextSibling); } | |
| function extend(o, p, q) { if (!p) return o; for (var i in p) { if (isObject(p[i])) { if (!o[i]) o[i] = {}; extend(o[i], p[i]); } else { o[i] = p[i]; } } if (q) extend(o, q); return o; } | |
| var configuration = { | |
| registerExtenders: false, | |
| messagesOnModified: false, | |
| messageTemplate: null, | |
| insertMessages: false, | |
| parseInputAttributes: false | |
| }; | |
| ko.validation = { | |
| configure: function (options) { | |
| extend(configuration, options); | |
| ko.validation.registerExtenders(); | |
| ko.validation.registerValueBindingHandler(); | |
| }, | |
| group: function (obj) { // array of observables or viewModel | |
| var group = isArray(obj) ? obj : values(obj); | |
| var observables = ko.utils.arrayFilter(group, function (item) { | |
| if (ko.isObservable(item)) { | |
| item.extend({ validatable: true }); | |
| return true; | |
| } | |
| return false; | |
| }); | |
| var result = ko.dependentObservable(function () { | |
| var errors = []; | |
| ko.utils.arrayForEach(observables, function (observable) { | |
| var error = observable.isValid(); | |
| if (error) | |
| errors.push(error); | |
| }); | |
| return errors; | |
| }); | |
| result.showAllMessages = function () { | |
| ko.utils.arrayForEach(observables, function (observable) { | |
| observable.isModified(true); | |
| }); | |
| }; | |
| return result; | |
| }, | |
| formatMessage: function (message, params) { | |
| return message.replace('{0}', params); | |
| }, | |
| addRule: function (observable, rule) { | |
| observable.extend({ validatable: true }); | |
| observable.rules.push(rule); | |
| return observable; | |
| }, | |
| addExtender: function (name, validator, message) { | |
| ko.extenders[name] = function (observable, params) { | |
| return ko.validation.addRule(observable, { | |
| validator: validator, | |
| message: message, | |
| params: params | |
| }); | |
| }; | |
| }, | |
| registerExtenders: function () { // root extenders optional, use 'validation' extender if would cause conflicts | |
| if (configuration.registerExtenders) { | |
| for (var i in ko.validation.validators) { | |
| ko.validation.addExtender(i, ko.validation.validators[i], ko.validation.messages[i]); | |
| } | |
| } | |
| }, | |
| insertValidationMessage: function (element) { | |
| var span = document.createElement('SPAN'); | |
| span.className = 'validationMessage'; | |
| insertAfter(element, span); | |
| return span; | |
| }, | |
| registerValueBindingHandler: function () { // parse html5 input validation attributes where value binder, optional feature | |
| var init = ko.bindingHandlers.value.init; | |
| ko.bindingHandlers.value.init = function (element, valueAccessor, allBindingsAccessor, viewModel, options) { | |
| init(element, valueAccessor, allBindingsAccessor); | |
| var config = extend({}, configuration, options.validation); | |
| if (config.parseInputAttributes) { | |
| setTimeout(function () { | |
| ko.utils.arrayForEach(['required', 'min', 'max', 'maxLength', 'pattern'], function (attr) { | |
| if (hasAttribute(element, attr)) { | |
| ko.validation.addRule(valueAccessor(), { | |
| validator: ko.validation.validators[attr], | |
| message: ko.validation.messages[attr], | |
| params: element.getAttribute(attr) || true | |
| }); | |
| } | |
| }); | |
| }, 0); | |
| } | |
| if (config.insertMessages && isValidatable(valueAccessor())) { | |
| var validationMessageElement = ko.validation.insertValidationMessage(element); | |
| if (config.messageTemplate) { | |
| ko.renderTemplate(config.messageTemplate, { | |
| field: valueAccessor() | |
| }, null, validationMessageElement, 'replaceNode'); | |
| } else { | |
| ko.applyBindingsToNode(validationMessageElement, { | |
| validationMessage: valueAccessor() | |
| }); | |
| } | |
| } | |
| }; | |
| }, | |
| messages: { // allows localization | |
| required: 'This field is required.', | |
| min: 'Please enter a value greater than or equal to {0}.', | |
| max: 'Please enter a value less than or equal to {0}.', | |
| minLength: 'Please enter at least {0} characters.', | |
| maxLength: 'Please enter no more than {0} characters.', | |
| pattern: 'Please check this value.' | |
| }, | |
| validators: { | |
| required: function (val, required) { | |
| return required && val && val.length > 0; | |
| }, | |
| min: function (val, min) { | |
| return !val || val >= min; | |
| }, | |
| max: function (val, max) { | |
| return !val || val <= max; | |
| }, | |
| minLength: function (val, minLength) { | |
| return val && val.length >= minLength; | |
| }, | |
| maxLength: function (val, maxLength) { | |
| return !val || val.length <= maxLength; | |
| }, | |
| pattern: function (val, regex) { | |
| return !val || val.match(regex) != null; | |
| } | |
| } | |
| }; | |
| ko.bindingHandlers.validationMessage = { // individual error message, if modified or post binding | |
| update: function (element, valueAccessor) { | |
| valueAccessor().extend({ validatable: true }); | |
| ko.bindingHandlers.text.update(element, function () { | |
| return !configuration.messagesOnModified || valueAccessor().isModified() ? valueAccessor().isValid : null; | |
| }); | |
| } | |
| }; | |
| ko.bindingHandlers.validationOptions = { | |
| init: function (element, valueAccessor, allBindingsAccessor, viewModel, options) { | |
| return { 'controlsDescendantBindings': true } | |
| }, | |
| update: function (element, valueAccessor, allBindingsAccessor, viewModel, options, descendantBindingContext) { | |
| var localOptions = extend({}, options, { validation: valueAccessor() }); | |
| ko.applyBindingsToDescendants(viewModel, element, localOptions); | |
| } | |
| }; | |
| ko.extenders.validation = function (observable, rules) { // allow single rule or array | |
| ko.utils.arrayForEach(isArray(rules) ? rules : [rules], function (rule) { | |
| ko.validation.addRule(observable, rule); | |
| }); | |
| return observable; | |
| }; | |
| ko.extenders.validatable = function (observable, enable) { | |
| if (enable && !isValidatable(observable)) { | |
| observable.rules = ko.observableArray(); | |
| observable.isValid = ko.dependentObservable(function () { | |
| for (var i = 0; i < observable.rules().length; i++) { | |
| var r = observable.rules()[i]; | |
| if (!r.validator(observable(), r.params || true)) // default param is true, eg. required = true | |
| return ko.validation.formatMessage(r.message, r.params); | |
| } | |
| return null; | |
| }); | |
| observable.isModified = ko.observable(false); | |
| observable.subscribe(function (newValue) { | |
| observable.isModified(true); | |
| }); | |
| } | |
| return observable; | |
| } | |
| })(); |
Hi Eric
This remains the most up-to-date version of the Knockout validation prototype I started. There have been no changes recently. Would be great if you found it useful to build upon.
Andy
Is there any documentation, blog post or forum post about this plugin? Looks pretty sweet!
This validation prototype relates to the discussion thread at https://groups.google.com/d/topic/knockoutjs/28TNX4eQ9sI/discussion and the sample at http://jsfiddle.net/andybooth/2GUyX.
Thanks. Really like this!
One thing I do miss or haven't found out is to be able to set individual validation messages on the html element, but to keep all rules in the model. The main reason I wan't this is to make my models more reusable and put design and copy in the html, but logic in js.
Any ideas on how to implement this thru a element attribute or a ko custom binding?
// Johan
Andy,
Took a bit longer than I was hoping, but I've updated this on my fork at https://gist.github.com/1301497
There were some breaking changes between the Knockout 1.3 CTP that you have in the fiddle and what the latest is in the Knockout repo (looks like Steve is doing several pull requests tonight as well, so hopefully this all still works...)
- I re-wrote the
validationOptionsbinding as theko.applyBindingsToDescendantsmethod in the CTP no longer seems to exist. I instead went with embedding awithbinding, and so hopefully its a little more extensible going forward. - I added a lot of comments to make it easier for me to remember what logic was doing in certain places... and hopefully give some examples to folks as they read the source.
- I changed how rules are stored in the plugin so that it more closely resembles (hopefully this change isn't too opinionated) implementing a custom
bindingHandler.validator: function( val, mustEqualVal ){ return val === mustEqualVal; }, message: 'This field must equal {0}' };
Anyways, I also tweaked a few other things to give some easier syntax when defining bindings (but left full backward compatibility as much as I can tell), you can see the fiddle here: http://jsfiddle.net/ericbarnard/KHFn8/
Finally, I did put together a repo w/ a Unit Test suite here: https://github.com/ericmbarnard/Knockout-Validation
You started this project, so if you want to start your own repo or pull the bits out of mine and start one... I will get rid of mine and issue pull requests to yours.
Thanks for getting this started!
Eric,
Thanks for adding momentum and improvements to the plugin. Using the "with" binding for the cascading validation options is especially cool.
Going forward, if it meets your requirements and you have little bits of time to work on it, please do circulate https://github.com/ericmbarnard/Knockout-Validation and the JsFiddle on the KnockoutJS Google Group as the new home of the plugin.
Andy
Andy,
Thanks a ton! My company is looking at leveraging this in our existing application, so I should be able to keep it up to date. Thanks for all the work!
Andy,
Is this still Active, and have you and Steve worked on anything with it recently?
I was thinking about making some contributions, but I wasn't sure if this was the most mature version of the validation plugin for 1.3.