5

有没有人编写过一个实用程序,可以将 Breeze 元数据(从实体框架数据属性中捕获)转换为敲除验证扩展(使用 knockout.validation)?

4

6 回答 6

8

我制作了一个从实体读取元数据并添加验证规则的函数。

app.domain.indicador = (function () {
"use strict";
var constructor = function () {...}
var initializer = function indicadorInitializer(entity) {
    var entityType = entity.entityType;
    if (entityType) {
        console.log(entityType);
        for (var i = 0; i < entityType.dataProperties.length; i++) {
            var property = entityType.dataProperties[i];
            console.log(property);
            var propertyName = property.name;

            var propertyObject = entity[propertyName];
            if (!property.isNullable) {
                propertyObject.extend({ required: true });
            }
            if (property.maxLength) {
                propertyObject.extend({ maxLength: property.maxLength });
            }
        }

        for (var i = 0; i < entityType.foreignKeyProperties.length; i++) {
            var property = entityType.foreignKeyProperties[i];
            console.log(property);
            var propertyName = property.name;

            var propertyObject = entity[propertyName];
            if (!property.isNullable) {
                propertyObject.extend({ required: true });
            }
            if (property.maxLength) {
                propertyObject.extend({ maxLength: property.maxLength });
            }
            //Bussines rule
            propertyObject.extend({ notEqual: 0 });
        }
    }
};
return {
    constructor: constructor,
    initializer: initializer
};
})();

我使用该函数作为初始化程序:

store.registerEntityTypeCtor("Indicador", domain.indicador.constructor, domain.indicador.initializer);

这只是一个开始,但时间对我很有用。

更新:

我改变了添加验证的方式。我在这里分享它以防它对某人有用:

辅助对象:

app.validatorHelper = (function (breeze) {
var foreignKeyInvalidValue = 0;

function addDataTypeRules(dataType, property) {
    switch (dataType) {
        case breeze.DataType.DateTime:
            //TODO: implement my function to validate dates. This validator is too permissive
            property.extend({ date: true });
            break;
        case breeze.DataType.Int64:
        case breeze.DataType.Int32:
        case breeze.DataType.Int16:
            //it's needed to accept negative numbers because of the autogenerated keys
            property.extend({ signedDigit: true });
            break;
        case breeze.DataType.Decimal:
        case breeze.DataType.Double:
        case breeze.DataType.Single:
            property.extend({ number: true });
            break;
    }
};

function addValidationRules(entity) {
    var entityType = entity.entityType;
    if (entityType) {
        for (var i = 0; i < entityType.dataProperties.length; i++) {
            var property = entityType.dataProperties[i];
            //console.log(property);
            var propertyName = property.name;
            var propertyObject = entity[propertyName];

            addDataTypeRules(property.dataType, propertyObject);

            if (!property.isNullable) {
                propertyObject.extend({ required: true });
            }
            if (property.maxLength) {
                propertyObject.extend({ maxLength: property.maxLength });
            }
        }

        for (var i = 0; i < entityType.foreignKeyProperties.length; i++) {
            var property = entityType.foreignKeyProperties[i];
            //console.log(property);
            var propertyName = property.name;
            var propertyObject = entity[propertyName];

            addDataTypeRules(property.dataType, propertyObject);

            if (!property.isNullable) {
                propertyObject.extend({ required: true });
                //Bussiness Rule: 0 is not allowed for required foreign keys
                propertyObject.extend({ notEqual: foreignKeyInvalidValue });
            }
            if (property.maxLength) {
                propertyObject.extend({ maxLength: property.maxLength });
            }
        }
    }
};

return {
    addValidationRules: addValidationRules
};
})(breeze);

自定义验证器:

(function (ko) {
ko.validation.rules['signedDigit'] = {
    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.registerExtenders();
})(ko);

在初始化程序中使用帮助程序:

app.domain.valorIndicador = (function (vHelper) {
"use strict";
var constructor = function () {
};

var initializer = function indicadorInitializer(entity) {
    vHelper.addValidationRules(entity);
};

return {
    constructor: constructor,
    initializer: initializer
};
})(app.validatorHelper);

并设置初始化程序:

store.registerEntityTypeCtor("ValorIndicador", domain.valorIndicador.constructor, domain.valorIndicador.initializer);
于 2013-01-10T16:55:41.263 回答
2

一种使用敲除绑定来自微风js的验证错误的简单方法。

我们可以从 entityAspect订阅validationErrorsChanged事件:

function subscribeValidation() {
    return self.entity().entityAspect.validationErrorsChanged.subscribe(function (validationChangeArgs) {
                validationChangeArgs.added.forEach(function (item) { addError(item); });
                validationChangeArgs.removed.forEach(function (item) { self.validationErrors.remove(item); });
    });
}

this.hasError = function (propertyName) {
    var array = self.validationErrors();
    var match = array.filter(function (item) {
        return item.propertyName == propertyName;
    });
    if (match.length > 0) {
        return true;
    } else return false;
};


function addError(item) {
    self.validationErrors.remove(function (i) {
        return i.propertyName == item.propertyName;
    });

    self.validationErrors.push(item);
}

最后我们可以绑定到 UI 上的消息(我正在使用 Twitter boostrap css 类)

<div class="control-group" data-bind="css: { 'error': hasError('Nome') }">
    <label class="control-label">Nome</label>
    <div class="controls">
        <input type="text" class="input-xxlarge" data-bind="value: model().Nome">
        <span class="help-inline" data-bind="text: getErrorMessage('Nome')"></span>
    </div>
</div>

在这里查看完整的要点

于 2013-03-13T19:39:39.127 回答
2

我之前搜索过这个,因为我开始使用带有敲除的微风,然后我对如何验证内容以及如何显示内联验证有完全相同的问题。

考虑到微风已经内置了验证,我决定编写一个自定义的 Knockout Binding 来在每次可观察值发生变化时显示验证结果,毕竟这很容易:

这是自定义绑定:

    ko.bindingHandlers.breezeValidate = {
    init: function (element, valueAccessor, allBindingsAccessor, context) {
        var isOk = context.entityAspect.validateProperty(valueAccessor());
        var errors = context.entityAspect.getValidationErrors(valueAccessor());
        var message = "";
        if (errors.length > 0)
            message = errors[0].errorMessage;
        $(element).html(message);
    },

    //update the control when the view model changes
    update: function (element, valueAccessor, allBindingsAccessor, context) {
        debugger;
        this.init(element, valueAccessor, allBindingsAccessor, context)
    }
};

用法是这样的:

<span data-bind="text: Name"></span>
<span data-bind="breezeValidate: 'Name'"></span>

这是因为这一行:

var isOk = context.entityAspect.validateProperty(valueAccessor());

当微风被请求验证属性时,它最终会调用 observable 并通过敲除注册,因此每次更改时,都会再次调用此绑定并相应地更新错误消息。

我只是展示了第一条验证消息,但当然您可以遍历所有这些消息,甚至为元素添加不同的样式。

希望这可以帮助!!

于 2013-09-06T12:56:44.957 回答
2

不知道为什么人们会想要使用 ko.validation - 它只是复制了处理微风的客户端正在做的事情。鉴于微风开发人员暗示验证将很快获得更大的力量,何必费心呢。

所以我从蒂亚戈奥利维拉的伟大作品开始。但我希望有最低限度的标记。通过假设使用引导类并默认上一个元素的验证属性名称,我可以将大多数标记添加简化为:

<span class="help-inline" data-bind="breezeValidation: null"></span>

赢!

我的 ko.bindingHandler:

//Highlight field in red & show first validation message
//
//Outputs first validation message for 'propertyName' or if null: previous controls value binding
//Needs ancestor with 'control-group' class to set class 'error' for Bootstrap error display
//
//Example:
//<td class="control-group">
//    <input class="input-block-level text-right" data-bind="value: id" />
//    <span class="help-inline" data-bind="breezeValidation: null"></span>
//</td>
//
//Does not and cannot validate keys that already exist in cache. knockout write calls breeze which throws uncaught error

ko.bindingHandlers.breezeValidation = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here

        var $msgElement = $(element);
        var entity = viewModel;

        var propName = valueAccessor();
        if (propName === null) {
            //  $element.prev().data("bind") = "value: itemType"
            var prevBinds = $msgElement.prev().data("bind");
            if (!prevBinds) {
                $msgElement.text("Could not find prev elements binding value.");
                return;
            }
            var bindPhrases = prevBinds.split(/,/);
            for (var i = 0, j = bindPhrases.length; i < j; i++) {
                var bindPhrase = bindPhrases[i];
                if (utility.stringStartsWith(bindPhrase, 'value: ')) {
                    propName = bindPhrase.substr(7);
                    break;
                }
            }
        }

        if (!propName) {
            $msgElement.text("Could not find this or prev elements binding value.");
            return;
        }

        //var $groupElement = $msgElement.parent();      
        var $groupElement = $msgElement.closest(".control-group");
        if (!$groupElement.hasClass("control-group")) {
            $msgElement.text("Could not find parent with 'control-group' class.");
            return;
        }


        onValidationChange();               //fire immediately (especially for added)
                                            //... and anytime validationErrors are changed fire onValidationChnange
        entity.entityAspect.validationErrorsChanged.subscribe(onValidationChange);

        element.onchange = function () {
            //Should never have updates pushed from validation msgElement
            $msgElement.text("readonly error");
        };


        function onValidationChange() {
            var errors = entity.entityAspect.getValidationErrors(propName);
            var message = "";
            if (errors.length > 0) {
                message = errors[0].errorMessage;
            }

            if (message) {
                $groupElement.addClass('error');
            }
            else {
                $groupElement.removeClass('error');
            }

            $msgElement.text(message);
        }


    }
    //Not interested in changes to valueAccessor - it is only the fieldName.
    //update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
};

示例视图简单的隐式属性用法:

<div class="control-group">
    <label class="control-label" for="editStatusNote">Status note:</label>
    <div class="controls">
        <input id="editStatusNote" type="text" data-bind="value: statusNote" />
        <span class="help-inline" data-bind="breezeValidation: null"></span>
    </div>
</div>

示例视图显式属性用法:

<div class="control-group">
    <label class="control-label" for="editAmount">Amount:</label>
    <div class="controls">
        <div class="input-prepend">
            <span class="add-on">$</span>
            <input id="editAmount" class="input-small" type="text" data-bind="value: amount" />
        </div>
        <span class="help-inline" data-bind="breezeValidation: 'amount'"></span>
    </div>
</div>                        
于 2013-09-19T04:02:17.503 回答
0

我将breathValidation 更新为Bootstrap 3,并通过多路径属性支持进行了改进。

ko.bindingHandlers.breezeValidation = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // This will be called when the binding is first applied to an element
        // Set up any initial state, event handlers, etc. here

        var $msgElement = $(element);
        var entity = viewModel;

        var propName = valueAccessor();
        if (propName === null) {
            //  $element.prev().data("bind") = "value: itemType"
            var prevBinds = $msgElement.prev().data("bind");
            if (!prevBinds) {
                $msgElement.text("Could not find prev elements binding value.");
                return;
            }
            var bindPhrases = prevBinds.split(/,/);
            for (var i = 0, j = bindPhrases.length; i < j; i++) {
                var bindPhrase = bindPhrases[i];
                if (bindPhrase.substr(0, 7) == 'value: ') {
                    propName = bindPhrase.substr(7);

                    entity = ko.utils.unwrapObservable(entity);
                    var propPath = propName.replace(/[()]/g, "").split('.'), i = 0;
                    var tempProp = entity[propPath[i]], links = propPath.length;
                    i++;
                    while (ko.utils.unwrapObservable(tempProp) && i < links) {
                        entity = ko.utils.unwrapObservable(tempProp);
                        tempProp = entity[propName = propPath[i]];
                        i++;
                    }

                    break;
                }
            }
        }

        if (!propName) {
            $msgElement.text("Could not find this or prev elements binding value.");
            return;
        }

        //var $groupElement = $msgElement.parent();      
        var $groupElement = $msgElement.closest(".form-group");
        if (!$groupElement.hasClass("form-group")) {
            $msgElement.text("Could not find parent with 'form-group' class.");
            return;
        }


        onValidationChange();               //fire immediately (especially for added)
        //... and anytime validationErrors are changed fire onValidationChnange
        entity.entityAspect.validationErrorsChanged.subscribe(onValidationChange);

        element.onchange = function () {
            //Should never have updates pushed from validation msgElement
            $msgElement.text("readonly error");
        };


        function onValidationChange() {
            var errors = entity.entityAspect.getValidationErrors(propName);
            var message = "";
            if (errors.length > 0) {
                message = errors[0].errorMessage;
            }

            if (message) {
                $groupElement.addClass('has-error');
            }
            else {
                $groupElement.removeClass('has-error');
            }

            $msgElement.text(message);
        }


    }
    //Not interested in changes to valueAccessor - it is only the fieldName.
    //update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
};
于 2014-02-18T09:54:16.863 回答
0

Knockout 验证器可以整体使用微风验证:

function addKoValidationRules(entity) {
    if (entity.koValidationRulesAdded) {
        return;
    }
    entity.entityType.dataProperties.forEach(function (property) {
        entity[property.name].extend({
            validation: {
                validator: function () {
                    // manual validation ensures subscription to observables which current field depends on
                    // entity is added to context for retrieving other properties in custom validators
                    entity.entityAspect.validateProperty(property.name, { entity: entity });
                    var errors = entity.entityAspect.getValidationErrors(property.name);
                    if (!errors.length) {
                        return true;
                    }
                    this.message = errors[0].errorMessage;
                    return false;
                },
                message: ''
            }
        });
    });
    entity.koValidationRulesAdded = true;
}
于 2015-07-15T09:17:47.930 回答