zoukankan      html  css  js  c++  java
  • MVVM架构~knockoutjs系列之从Knockout.Validation.js源码中学习它的用法

    返回目录

    说在前

    有时,我们在使用一个插件时,在网上即找不到它的相关API,这时,我们会很抓狂的,与其抓狂,还不如踏下心来,分析一下它的源码,事实上,对于JS这种开发语言来说,它开发的插件的使用方法都在它的源码里,只要你踏下心去看,一切就都有了!

    Knockout.Validation.js是为Knockout插件服务的,它可以为Knockout对象进行验证,就像你使用MVC模型验证一样,而这种绑定的验证方式对于开发人员来说是很容易接受的,也是一种趋势,它在验证过程中,会将出现异常的点记录下来,然后在

    某个时候将它抛出来,这个抛出的时刻通常是对象失去焦点时(blur)。

    总结Knockout.Validation.js几个常用的东西

    为空验证

        self.CategoryId = ko.observable().extend({
                required: true
            });

    最大最小值验证

          self.price = ko.observable().extend({
                required: { params: true, message: "请输入价格" },
                min: { params: 1, message: "请输入大于1的整数" },
                max: 100
            });

    长度验证

          self.name = ko.observable().extend({
                minLength: 2,
                maxLength: { params: 30, message: "名称最大长度为30个字符" },
                required: {
                    params: true,
                    message: "请输入名称",
                }
            });

    电话验证

       self.phone = ko.observable().extend({
                phoneUS: {
                    params: true,
                    message: "电话不合法",
                }
            });

    邮箱验证

       self.Email = ko.observable().extend({
                required: {
                    params: true,
                    message: "请填写Email"
                },
                email: {
                    params: true,
                    message: "Email格式不正确"
                }
            });

    数字验证

         self.age = ko.observable().extend({
                number: {
                    params: true,
                    message: "必须是数字",
                }
            });

    相等验证

     self.PayPassword = ko.observable().extend({
                required: {
                    params: true,
                    message: "请填写支付密码"
                },
                equal:{
                    params:"zzl",
                    message:"支付密码错误"
                }

    事实上,Knockout.Validation.js还有包括range,date,digit,notEqual等验证,都大同小意,我就不一一说了。

    Knockout.Validation.js源码

    /*=============================================================================
        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;
    
        var kv = ko.validation,
            koUtils = ko.utils,
            unwrap = koUtils.unwrapObservable,
            forEach = koUtils.arrayForEach,
            extend = koUtils.extend;
    ;/*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
        decorateInputElement: 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
        allowHtmlMessages: false,        // allows HTML in validation messages
        grouping: {
            deep: false,        //by default grouping is shallow
            observable: true,   //and using observables
            live: false            //react to changes to observableArrays if observable === true
        },
        validate: {
            // throttle: 10
        }
    };
    
    // make a copy  so we can use 'reset' later
    var configuration = extend({}, defaults);
    
    configuration.html5Attributes = ['required', 'pattern', 'min', 'max', 'step'];
    configuration.html5InputTypes = ['email', 'number', 'date'];
    
    configuration.reset = function () {
        extend(configuration, defaults);
    };
    
    kv.configuration = configuration;
    ;kv.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';
            },
            isObservableArray: function(instance) {
                return !!instance &&
                        typeof instance["remove"] === "function" &&
                        typeof instance["removeAll"] === "function" &&
                        typeof instance["destroy"] === "function" &&
                        typeof instance["destroyAll"] === "function" &&
                        typeof instance["indexOf"] === "function" &&
                        typeof instance["replace"] === "function";
            },
            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 = kv.utils.contextFor(element);
    
                return options || kv.configuration;
            },
            setDomData: function (node, data) {
                var key = node[domDataKey];
    
                if (!key) {
                    node[domDataKey] = key = kv.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 = kv.utils.getDomData(node);
                        if (context) { return context; }
                        if (node.parentNode) { return kv.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 = kv.utils.getAttribute(element, 'data-orig-title'),
                    currentTitle = element.title,
                    hasSavedOriginalTitle = kv.utils.hasAttribute(element, 'data-orig-title');
    
                return hasSavedOriginalTitle ?
                    savedOriginalTitle : currentTitle;
            },
            async: function (expr) {
                if (window.setImmediate) { window.setImmediate(expr); }
                else { window.setTimeout(expr, 0); }
            },
            forEach: function (object, callback) {
                if (kv.utils.isArray(object)) {
                    return forEach(object, callback);
                }
                for (var prop in object) {
                    if (object.hasOwnProperty(prop)) {
                        callback(object[prop], prop);
                    }
                }
            }
        };
    }());;var api = (function () {
    
        var isInitialized = 0,
            configuration = kv.configuration,
            utils = kv.utils;
    
        function cleanUpSubscriptions(context) {
            forEach(context.subscriptions, function (subscription) {
                subscription.dispose();
            });
            context.subscriptions = [];
        }
    
        function dispose(context) {
            if (context.options.deep) {
                forEach(context.flagged, function (obj) {
                    delete obj.__kv_traversed;
                });
                context.flagged.length = 0;
            }
    
            if (!context.options.live) {
                cleanUpSubscriptions(context);
            }
        }
    
        function runTraversal(obj, context) {
            context.validatables = [];
            cleanUpSubscriptions(context);
            traverseGraph(obj, context);
            dispose(context);
            }
    
        function traverseGraph(obj, context, level) {
            var objValues = [],
                val = obj.peek ? obj.peek() : obj;
    
            if (obj.__kv_traversed === true) { return; }
    
            if (context.options.deep) {
            obj.__kv_traversed = true;
                context.flagged.push(obj);
            }
    
            //default level value depends on deep option.
            level = (level !== undefined ? level : context.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 }); }
                context.validatables.push(obj);
    
                if(context.options.live && utils.isObservableArray(obj)) {
                    context.subscriptions.push(obj.subscribe(function () {
                        context.graphMonitor.valueHasMutated();
                    }));
            }
            }
    
            //get list of values either from array or object but ignore non-objects
            // and destroyed objects
            if (val && !val._destroy) {
                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) {
                utils.forEach(objValues, function (observable) {
    
                    //but not falsy things and not HTML Elements
                    if (observable && !observable.nodeType) {
                        traverseGraph(observable, context, level + 1);
                    }
                });
            }
        }
    
        function collectErrors(array) {
            var errors = [];
            forEach(array, function (observable) {
                if (!observable.isValid()) {
                    errors.push(observable.error());
                }
            });
            return errors;
        }
    
        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;
    
                extend(configuration, options);
    
                if (configuration.registerExtenders) {
                    kv.registerExtenders();
                }
    
                isInitialized = 1;
            },
            // backwards compatability
            configure: function (options) { kv.init(options); },
    
            // resets the config back to its original state
            reset: kv.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 = extend(extend({}, configuration.grouping), options);
    
                var context = {
                    options: options,
                    graphMonitor: ko.observable(),
                    flagged: [],
                    subscriptions: [],
                    validatables: []
            };
    
                var result = null;
    
                //if using observables then traverse structure once and add observables
                if (options.observable) {
                    runTraversal(obj, context);
    
                    result = ko.computed(function () {
                        context.graphMonitor(); //register dependency
                        runTraversal(obj, context);
    
                        return collectErrors(context.validatables);
                    });
    
                } else { //if not using observables then every call to error() should traverse the structure
                    result = function () {
                        runTraversal(obj, context);
    
                        return collectErrors(context.validatables);
                    };
                }
    
                result.showAllMessages = function (show) { // thanks @heliosPortal
                    if (show === undefined) {//default to true
                        show = true;
                    }
    
                    // ensure we have latest changes
                    result();
    
                    forEach(context.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();
    
                    invalidAndModifiedPresent = !!koUtils.arrayFirst(context.validatables, function (observable) {
                        return !observable.isValid() && observable.isModified();
                    });
                    return invalidAndModifiedPresent;
                };
    
                return result;
            },
    
            formatMessage: function (message, params, observable) {
                if (typeof (message) === 'function') {
                    return message(params, observable);
                }
                return message.replace(/{0}/gi, unwrap(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: kv.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 'kv.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) {
                if (ruleObj['message'] === undefined) {
                    ruleObj['message'] = 'Error';
                }
    
                //make sure onlyIf is honoured
                if (ruleObj.onlyIf) {
                    ruleObj.condition = ruleObj.onlyIf;
                }
    
                //add the anonymous rule to the observable
                kv.addRule(observable, ruleObj);
            },
    
            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 && (params.message || params.onlyIf)) { //if it has a message or condition object, then its an object literal to use
                        return kv.addRule(observable, {
                            rule: ruleName,
                            message: params.message,
                            params: utils.isEmptyVal(params.params) ? true : params.params,
                            condition: params.onlyIf
                        });
                    } else {
                        return kv.addRule(observable, {
                            rule: ruleName,
                            params: params
                        });
                    }
                };
            },
    
            // loops through all kv.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 kv.rules) {
                        if (kv.rules.hasOwnProperty(ruleName)) {
                            if (!ko.extenders[ruleName]) {
                                kv.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) {
                forEach(kv.configuration.html5Attributes, function (attr) {
                    if (utils.hasAttribute(element, attr)) {
    
                        var params = element.getAttribute(attr) || true;
    
                        if (attr === 'min' || attr === 'max')
                        {
                            // If we're validating based on the min and max attributes, we'll
                            // need to know what the 'type' attribute is set to
                            var typeAttr = element.getAttribute('type');
                            if (typeof typeAttr === "undefined" || !typeAttr)
                            {
                                // From http://www.w3.org/TR/html-markup/input:
                                //   An input element with no type attribute specified represents the 
                                //   same thing as an input element with its type attribute set to "text".
                                typeAttr = "text"; 
                            }                            
                            params = {typeAttr: typeAttr, value: params}; 
                        }
                    
                        kv.addRule(valueAccessor(), {
                            rule: attr,
                            params: params
                        });
                    }
                });
    
                var currentType = element.getAttribute('type');
                forEach(kv.configuration.html5InputTypes, function (type) {
                    if (type === currentType) {
                        kv.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
                forEach(kv.configuration.html5Attributes, function (attr) {
                    var params;
                    var ctx = koUtils.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);
                };
            },
    
            // visit an objects properties and apply validation rules from a definition
            setRules: function (target, definition) {
                var setRules = function (target, definition) {
                    if (!target || !definition) { return; }
    
                    for (var prop in definition) {
                        if (!definition.hasOwnProperty(prop)) { continue; }
                        var ruleDefinitions = definition[prop];
    
                        //check the target property exists and has a value
                        if (!target[prop]) { continue; }
                        var targetValue = target[prop],
                            unwrappedTargetValue = unwrap(targetValue),
                            rules = {},
                            nonRules = {};
    
                        for (var rule in ruleDefinitions) {
                            if (!ruleDefinitions.hasOwnProperty(rule)) { continue; }
                            if (kv.rules[rule]) {
                                rules[rule] = ruleDefinitions[rule];
                            } else {
                                nonRules[rule] = ruleDefinitions[rule];
                            }
                        }
    
                        //apply rules
                        if (ko.isObservable(targetValue)) {
                            targetValue.extend(rules);
                        }
    
                        //then apply child rules
                        //if it's an array, apply rules to all children
                        if (unwrappedTargetValue && utils.isArray(unwrappedTargetValue)) {
                            for (var i = 0; i < unwrappedTargetValue.length; i++) {
                                setRules(unwrappedTargetValue[i], nonRules);
                            }
                            //otherwise, just apply to this property
                        } else {
                            setRules(unwrappedTargetValue, nonRules);
                        }
                    }
                };
                setRules(target, definition);
            }
        };
    
    }());
    
    // expose api publicly
    extend(ko.validation, api);;//Validation Rules:
    // You can view and override messages or rules via:
    // kv.rules[ruleName]
    //
    // To implement a custom Rule, simply use this template:
    // kv.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:
    // kv.rules['mustEqual'] = {
    //      validator: function( val, mustEqualVal ){
    //          return val === mustEqualVal;
    //      },
    //      message: 'This field must equal {0}'
    // };
    //
    kv.rules = {};
    kv.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.'
    };
    
    function minMaxValidatorFactory(validatorName) {
        var isMaxValidation = validatorName === "max";
    
        return function (val, options) {
            if (kv.utils.isEmptyVal(val)) {
                return true;
            }
    
            var comparisonValue, type;
            if (options.typeAttr === undefined) {
                // This validator is being called from javascript rather than
                // being bound from markup
                type = "text";
                comparisonValue = options;
            } else {
                type = options.typeAttr;
                comparisonValue = options.value;
            }
    
            // From http://www.w3.org/TR/2012/WD-html5-20121025/common-input-element-attributes.html#attr-input-min,
            // if the value is parseable to a number, then the minimum should be numeric
            if (!isNaN(comparisonValue)) {
                type = "number";
            }
    
            var regex, valMatches, comparisonValueMatches;
            switch (type.toLowerCase()) {
                case "week":
                    regex = /^(d{4})-W(d{2})$/;
                    valMatches = val.match(regex);
                    if (valMatches === null) {
                        throw "Invalid value for " + validatorName + " attribute for week input.  Should look like " +
                            "'2000-W33' http://www.w3.org/TR/html-markup/input.week.html#input.week.attrs.min";
                    }
                    comparisonValueMatches = comparisonValue.match(regex);
                    // If no regex matches were found, validation fails
                    if (!comparisonValueMatches) {
                        return false;
                    }
    
                    if (isMaxValidation) {
                        return (valMatches[1] < comparisonValueMatches[1]) || // older year
                            // same year, older week
                            ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2]));
                    } else {
                        return (valMatches[1] > comparisonValueMatches[1]) || // newer year
                            // same year, newer week
                            ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
                    }
                    break;
    
                case "month":
                    regex = /^(d{4})-(d{2})$/;
                    valMatches = val.match(regex);
                    if (valMatches === null) {
                        throw "Invalid value for " + validatorName + " attribute for month input.  Should look like " +
                            "'2000-03' http://www.w3.org/TR/html-markup/input.month.html#input.month.attrs.min";
                    }
                    comparisonValueMatches = comparisonValue.match(regex);
                    // If no regex matches were found, validation fails
                    if (!comparisonValueMatches) {
                        return false;
                    }
    
                    if (isMaxValidation) {
                        return ((valMatches[1] < comparisonValueMatches[1]) || // older year
                            // same year, older month
                            ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] <= comparisonValueMatches[2])));
                    } else {
                        return (valMatches[1] > comparisonValueMatches[1]) || // newer year
                            // same year, newer month
                            ((valMatches[1] === comparisonValueMatches[1]) && (valMatches[2] >= comparisonValueMatches[2]));
                    }
                    break;
    
                case "number":
                case "range":
                    if (isMaxValidation) {
                        return (!isNaN(val) && parseFloat(val) <= parseFloat(comparisonValue));
                    } else {
                        return (!isNaN(val) && parseFloat(val) >= parseFloat(comparisonValue));
                    }
                    break;
    
                default:
                    if (isMaxValidation) {
                        return val <= comparisonValue;
                    } else {
                        return val >= comparisonValue;
                    }
            }
        };
    }
    
    kv.rules['min'] = {
        validator: minMaxValidatorFactory("min"),
        message: 'Please enter a value greater than or equal to {0}.'
    };
    
    kv.rules['max'] = {
        validator: minMaxValidatorFactory("max"),
        message: 'Please enter a value less than or equal to {0}.'
    };
        
    kv.rules['minLength'] = {
        validator: function (val, minLength) {
            return kv.utils.isEmptyVal(val) || val.length >= minLength;
        },
        message: 'Please enter at least {0} characters.'
    };
    
    kv.rules['maxLength'] = {
        validator: function (val, maxLength) {
            return kv.utils.isEmptyVal(val) || val.length <= maxLength;
        },
        message: 'Please enter no more than {0} characters.'
    };
    
    kv.rules['pattern'] = {
        validator: function (val, regex) {
            return kv.utils.isEmptyVal(val) || val.toString().match(regex) !== null;
        },
        message: 'Please check this value.'
    };
    
    kv.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 (kv.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}'
    };
    
    kv.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 kv.utils.isEmptyVal(val) || (
                // jquery validate regex - thanks Scott Gonzalez
                validate && /^((([a-z]|d|[!#$%&'*+-/=?^_`{|}~]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])+(.([a-z]|d|[!#$%&'*+-/=?^_`{|}~]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])+)*)|((x22)((((x20|x09)*(x0dx0a))?(x20|x09)+)?(([x01-x08x0bx0cx0e-x1fx7f]|x21|[x23-x5b]|[x5d-x7e]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(\([x01-x09x0bx0cx0d-x7f]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF]))))*(((x20|x09)*(x0dx0a))?(x20|x09)+)?(x22)))@((([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])*([a-z]|d|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF]))).)+(([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])|(([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])([a-z]|d|-|.|_|~|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])*([a-z]|[u00A0-uD7FFuF900-uFDCFuFDF0-uFFEF])))$/i.test(val)
            );
        },
        message: 'Please enter a proper email address'
    };
    
    kv.rules['date'] = {
        validator: function (value, validate) {
            if (!validate) { return true; }
            return kv.utils.isEmptyVal(value) || (validate && !/Invalid|NaN/.test(new Date(value)));
        },
        message: 'Please enter a proper date'
    };
    
    kv.rules['dateISO'] = {
        validator: function (value, validate) {
            if (!validate) { return true; }
            return kv.utils.isEmptyVal(value) || (validate && /^d{4}[/-]d{1,2}[/-]d{1,2}$/.test(value));
        },
        message: 'Please enter a proper date'
    };
    
    kv.rules['number'] = {
        validator: function (value, validate) {
            if (!validate) { return true; }
            return kv.utils.isEmptyVal(value) || (validate && /^-?(?:d+|d{1,3}(?:,d{3})+)?(?:.d+)?$/.test(value));
        },
        message: 'Please enter a number'
    };
    
    kv.rules['digit'] = {
        validator: function (value, validate) {
            if (!validate) { return true; }
            return kv.utils.isEmptyVal(value) || (validate && /^d+$/.test(value));
        },
        message: 'Please enter a digit'
    };
    
    kv.rules['phoneUS'] = {
        validator: function (phoneNumber, validate) {
            if (!validate) { return true; }
            if (kv.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'
    };
    
    kv.rules['equal'] = {
        validator: function (val, params) {
            var otherValue = params;
            return val === kv.utils.getValue(otherValue);
        },
        message: 'Values must equal'
    };
    
    kv.rules['notEqual'] = {
        validator: function (val, params) {
            var otherValue = params;
            return val !== kv.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
    kv.rules['unique'] = {
        validator: function (val, options) {
            var c = kv.utils.getValue(options.collection),
                external = kv.utils.getValue(options.externalValue),
                counter = 0;
    
            if (!val || !c) { return true; }
    
            koUtils.arrayFilter(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 ? 1 : 2);
        },
        message: 'Please make sure the value is unique.'
    };
    
    
    //now register all of these!
    (function () {
        kv.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 = kv.utils.getConfigOptions(element);
                var observable = valueAccessor();
    
                // parse html5 input validation attributes, optional feature
                if (config.parseInputAttributes) {
                    kv.utils.async(function () { kv.parseInputValidationAttributes(element, valueAccessor); });
                }
    
                // if requested insert message element and apply bindings
                if (config.insertMessages && kv.utils.isValidatable(observable)) {
    
                    // insert the <span></span>
                    var validationMessageElement = kv.insertValidationMessage(element);
    
                    // if we're told to use a template, make sure that gets rendered
                    if (config.messageTemplate) {
                        ko.renderTemplate(config.messageTemplate, { field: observable }, null, validationMessageElement, 'replaceNode');
                    } else {
                        ko.applyBindingsToNode(validationMessageElement, { validationMessage: observable });
                    }
                }
    
                // write the html5 attributes if indicated by the config
                if (config.writeInputAttributes && kv.utils.isValidatable(observable)) {
    
                    kv.writeInputValidationAttributes(element, valueAccessor);
                }
    
                // if requested, add binding to decorate element
                if (config.decorateInputElement && kv.utils.isValidatable(observable)) {
                    ko.applyBindingsToNode(element, { validationElement: observable });
                }
            }
        };
    
    }());
    
    // override for KO's default 'value' and 'checked' bindings
    kv.makeBindingHandlerValidatable("value");
    kv.makeBindingHandlerValidatable("checked");
    
    
    ko.bindingHandlers['validationMessage'] = { // individual error message, if modified or post binding
        update: function (element, valueAccessor) {
            var obsv = valueAccessor(),
                config = kv.utils.getConfigOptions(element),
                val = unwrap(obsv),
                msg = null,
                isModified = false,
                isValid = false;
    
            if (!obsv.isValid || !obsv.isModified) {
                throw new Error("Observable is not validatable");
            }
    
            isModified = obsv.isModified();
            isValid = obsv.isValid();
    
            var error = null;
            if (!config.messagesOnModified || isModified) {
                error = isValid ? null : obsv.error;
            }
    
            var isVisible = !config.messagesOnModified || isModified ? !isValid : false;
            var isCurrentlyVisible = element.style.display !== "none";
    
            if (config.allowHtmlMessages) {
                koUtils.setHtml(element, error);
            } else {
                ko.bindingHandlers.text.update(element, function () { return error; });
            }
    
            if (isCurrentlyVisible && !isVisible) {
                element.style.display = 'none';
            } else if (!isCurrentlyVisible && isVisible) {
                element.style.display = '';
            }
        }
    };
    
    ko.bindingHandlers['validationElement'] = {
        update: function (element, valueAccessor, allBindingsAccessor) {
            var obsv = valueAccessor(),
                config = kv.utils.getConfigOptions(element),
                val = unwrap(obsv),
                msg = null,
                isModified = false,
                isValid = false;
    
            if (!obsv.isValid || !obsv.isModified) {
                throw new Error("Observable is not validatable");
            }
    
            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);
    
                // css: { validationElement: false }
                css[config.errorElementClass] = shouldShow;
    
                return css;
            };
    
            //add or remove class on the element;
            ko.bindingHandlers.css.update(element, cssSettingsAccessor, allBindingsAccessor);
            if (!config.errorsAsTitle) { return; }
    
            ko.bindingHandlers.attr.update(element, function () {
                var
                    hasModification = !config.errorsAsTitleOnModified || isModified,
                    title = kv.utils.getOriginalElementTitle(element);
    
                if (hasModification && !isValid) {
                    return { title: obsv.error, 'data-orig-title': title };
                } else if (!hasModification || isValid) {
                    return { title: title, 'data-orig-title': null };
                }
            });
        }
    };
    
    // 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 = unwrap(valueAccessor());
                if (options) {
                    var newConfig = extend({}, kv.configuration);
                    extend(newConfig, options);
    
                    //store the validation options on the node so we can retrieve it later
                    kv.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
        forEach(kv.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
            kv.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, options) {
        if (!kv.utils.isObject(options)) {
            options = { enable: options };
        }
    
        if (!('enable' in options)) {
            options.enable = true;
        }
    
        if (options.enable && !kv.utils.isValidatable(observable)) {
            var config = kv.configuration.validate || {};
            var validationOptions = {
                throttleEvaluation : options.throttle || config.throttle
            };
    
            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);
    
            // a semi-protected observable
            observable.isValid = ko.computed(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);
                return observable;
            };
    
            //subscribe to changes in the observable
            var h_change = observable.subscribe(function () {
                observable.isModified(true);
            });
    
            // we use a computed here to ensure that anytime a dependency changes, the
            // validation logic evaluates
            var h_obsValidationTrigger = ko.computed(extend({
                read: function () {
                    var obs = observable(),
                        ruleContexts = observable.rules();
    
                    kv.validateObservable(observable);
    
                    return true;
                }
            }, validationOptions));
    
            extend(h_obsValidationTrigger, validationOptions);
    
            observable._disposeValidation = function () {
                //first dispose of the subscriptions
                observable.isValid.dispose();
                observable.rules.removeAll();
                if (observable.isModified.getSubscriptionsCount() > 0) {
                    observable.isModified._subscriptions['change'] = [];
                }
                if (observable.isValidating.getSubscriptionsCount() > 0) {
                    observable.isValidating._subscriptions['change'] = [];
                }
                if (observable.__valid__.getSubscriptionsCount() > 0) {
                    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 (options.enable === false && 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 : unwrap(ctx.params)))) { // default param is true, eg. required = true
    
            //not valid, so format the error message and stick it in the 'error' variable
            observable.setError(kv.formatMessage(
                        ctx.message || rule.message,
                        unwrap(ctx.params),
                        observable));
            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(kv.formatMessage(
                    msg || ctx.message || rule.message,
                    unwrap(ctx.params),
                    observable));
                observable.__valid__(isValid);
            }
    
            // tell it that we're done
            observable.isValidating(false);
        };
    
        //fire the validator and hand it the callback
        rule.validator(observable(), unwrap(ctx.params || true), callBack);
    }
    
    kv.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 = ctx.rule ? kv.rules[ctx.rule] : ctx;
    
            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.clearError();
        return true;
    };
    ;
    //quick function to override rule messages
    kv.localize = function (msgTranslations) {
    
        var msg, rule;
    
        //loop the properties in the object and assign the msg to the rule
        for (rule in msgTranslations) {
            if (kv.rules.hasOwnProperty(rule)) {
                kv.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];
            }
        }
    
        kv.init();
    
        if (config) { kv.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) {
    
        kv.init();
    
        origApplyBindings(viewModel, rootNode);
    };
    
    ko.validatedObservable = function (initialValue) {
        if (!kv.utils.isObject(initialValue)) { return ko.observable(initialValue).extend({ validatable: true }); }
    
        var obsv = ko.observable(initialValue);
        obsv.isValid = ko.observable();
        obsv.errors = kv.group(initialValue);
        obsv.errors.subscribe(function (errors) {
            obsv.isValid(errors.length === 0);
        });
    
        return obsv;
    };
    ;}));
    View Code

     返回目录

  • 相关阅读:
    golang json字符串合并操作
    goland 无法跳转 struct等
    golang 中mgo update报错: The dollar ($) prefixed field '$inc' in '$inc' is not valid for storage.
    解决windows下使用vscode没有函数提示的问题
    【转载,实测好用】gitlab结合sourcetree使用
    C++单继承、多继承情况下的虚函数表分析
    Linux 日志文件管理——限制大小
    C++ RCSP智能指针简单实现与应用
    Makefile模板(C++)
    Git关于pull,commit,push的总结
  • 原文地址:https://www.cnblogs.com/lori/p/3593807.html
Copyright © 2011-2022 走看看