有没有人编写过一个实用程序,可以将 Breeze 元数据(从实体框架数据属性中捕获)转换为敲除验证扩展(使用 knockout.validation)?
6 回答
我制作了一个从实体读取元数据并添加验证规则的函数。
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);
一种使用敲除绑定来自微风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>
在这里查看完整的要点
我之前搜索过这个,因为我开始使用带有敲除的微风,然后我对如何验证内容以及如何显示内联验证有完全相同的问题。
考虑到微风已经内置了验证,我决定编写一个自定义的 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 并通过敲除注册,因此每次更改时,都会再次调用此绑定并相应地更新错误消息。
我只是展示了第一条验证消息,但当然您可以遍历所有这些消息,甚至为元素添加不同的样式。
希望这可以帮助!!
不知道为什么人们会想要使用 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>
我将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) {
};
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;
}