I would like to create a custom simple constraint (like display and editable) which I can use within my Domain class. Is it possible to extend ConstrainedProperty class?
class City {
String title
BigDecimal latitude
BigDecimal longitude
Country country
static constraints = {
title ( blank: false, customConstraint: true ) // filter can be also be applied as attributes: [customConstraint: true]
}
}
Somebody familiar with this case?
To create your own constraint:
extend org.codehaus.groovy.grails.validation.AbstractConstraint
register the class as a constraint by calling org.codehaus.groovy.grails.validation.ConstrainedProperty.registerNewConstraint
Take a look at the grails implementation of the size constraint for an example.
Related
I've created a custom constraint (most code copied from NullableConstraint) that I'd like to use in my project.
How do I register this new constraint so I can use it in my domain classes and command objects? I've tried following this answer but that didn't work. I also tried copying the code from here but that didn't work either.
In your Config.groovy add the follow:
grails.gorm.default.constraints = {
validMobilePhone(validator: { value, obj, errors ->
if (!value) return
value = PhoneNumberUtils.sanitizeNumber(value)
if (!PhoneNumberUtils.validateMobilePhone(value)) {
errors.rejectValue('mobilePhone', "invalid")
}
})
nonNegative(min: new BigDecimal(0))
}
And then use this constraint in your domain:
static constraints = {
value shared: 'nonNegative'
}
I set up a few custom Constraint classes in a Grails 3 project and carried them forward to Grails 4.
In my Application class, I register the custom constraints to 2 constraint registries. There are a few methods on the Application class you can choose from to implement this. I don't know if my selection was the best.
void doWithDynamicMethods(){
//for domains
applicationContext.getBean(
org.grails.datastore.mapping.validation.ValidatorRegistry
).addConstraintFactory( someConstraintFactory )
//for command objects
(
(DefaultConstraintEvaluator)
applicationContext.getBean(
org.grails.datastore.gorm.validation.constraints.eval.ConstraintsEvaluator
)
).constraintRegistry.addConstraintFactory( someConstraintFactory )
}
I need to create a custom fields framework in my app. I defined a protocol for the fields called FieldType and extended with it UITextField and UIButton to be different types of fields.
Now I want to create a container view for the fields so I want the container to be able to refer to its field elements as both UIViews and FieldTypes, and I'm wondering if there's a concise way to define the type of elements it receives to be a specific UIView that implements the FieldType protocol?
I can have FieldContainerView accept UIViews or FieldTypes and check manually that it also matches the other with a guard statement, but it feels a bit cumbersome.
I tried 2 approaches:
1) Define a Custom Intermediary FieldViewType
The idea is to have FieldViewType extend UIView directly with FieldType so it might be useful as a general case for UITextField: FieldType and UIButton: FieldType. But as this code sample clearly shows, this does not work.
protocol FieldType {
var showError: Bool { get set }
var isEmpty: Bool { get set }
}
class CustomTextField: UITextField, FieldType {}
class CustomButtonField: UIButton, FieldType {}
let textField = CustomTextField()
textField is UIView // True
textField is FieldType // True
let buttonField = CustomButtonField()
buttonField is UIView // True
buttonField is FieldType // True
class FieldView: UIView, FieldProtocol {}
let field = FieldView()
field is UIView // True
field is FieldProtocol // True
textField is FieldView // False
buttonField is FieldView // False
2) Use Generics
I can define a generic type that matches the requirements like so <FieldViewType: UIView where FieldViewType: FieldType>, but I don't see where to use to best solve my problem. If I define it at the class level
class FieldContainerView<FieldViewType: UIView where FieldViewType: FieldType>: UIView {
var fields = [FieldViewType]()
func addField(FieldViewType: field) {
fields.append(field)
}
}
I need to declare the container class once for each field type I'll want to use and won't be able to use 2 field types in the same container.
The other option is to define type constraint at the function level with addField
class FieldContainerView: UIView {
var fields = [UIView]()
func addField<FieldViewType: UIView where FieldViewType: FieldType>(FieldViewType: field) {
fields.append(field)
}
}
and then cast each element in fields to FieldType when necessary and I'll know the cast will always work because addField is the only way to add elements to the container. But this also feels too cumbersome.
It feels like the best way around this would have been to be able to define FieldViewType with a typealias, but this doesn't seem to be supported. Or have UIView be defined with a protocol so it could be mixed better, but UIKit isn't constructed in this manner.
So it seems that at the moment there's no way to create a type constraint in property declarations. I don't know why, but I don't know anything about language implementations.
I went for a workaround where FieldType also has a view: UIView property with a default implementation.
The new FieldType declaration:
protocol FieldType {
var showError: Bool { get set }
var isEmpty: Bool { get set }
var view: UIView { get }
}
extension FieldType where Self: UIView {
var view: UIView {
return self
}
}
This way it doesn't matter from which class in the UIKit hierarchy you inherited before conforming to the FieldType protocol, as long as you have UIView somewhere as your super class, you'll have an accessibly view property.
This feels like a workaround, but at least it saves dual-declarations for collections that need both the FieldType and the UIView properties of an object.
Can you change this line:
class FieldView: UIView, FieldProtocol {}
To this:
class FieldView: UIView, FieldType {}
In order to support UTFMB8 encoding I added the following default constraint in Config.groovy
grails.gorm.default.constraints = {
'*'(maxSize: 191)
unlimitedSize(maxSize: Integer.MAX_VALUE)
}
I also added the unlimitedSize shared constraint which I use in some of my domain classes to override this default, e.g.
class BlogPost {
String body
static constraints = {
body shared: 'unlimitedSize'
}
}
However, there are a couple of classes in plugins that also need to override the default maxSize of 191. In these cases, I can't use the shared constraint, because I can't edit the source code. One option is to copy the classes into my application, and edit the copies (because artifacts in an application override those in plugins), but this is not very appealing because I've then effectively forked those classes.
Is there a better way? For example, is it possible for me to add constraints to these domain classes in Bootstrap.groovy?
Constraints can be added during boostrap something like:
class BootStrap {
def grailsApplication
def init = { servletContext ->
grailsApplication.domainClasses.each { GrailsDomainClass gdc ->
Class domainClass = gdc.clazz
if (domainClass.simpleName == 'BookFromPlugin') {
def field = domainClass.declaredFields.find {
it.name == 'body' && it.type == String
}
if (field) {
domainClass.constraints.body.maxSize = Integer.MAX_VALUE
}
}
}
}
}
where BookFromPlugin is a domain class from plugin and body is a property from the domain class. This can be optimized and made applicable to more than one domain class.
Since bootstrap is the last thing that is taken care of, it should eventually override the constraint previously defined in domain class.
UPDATE:
I guess you meant domainClass.constraints is accessing the private variable from domain class, but that is not true. domain.constraints gives a map where propery name is mapped to all its constraints. This is map is taken from ConstrainedProperty which is comprised of 3 elements: class owning the constraint, property name where constraint will be applied and the type of the property.
So when we use domain.constraints.body it actually gives a list of constraints applied to body as the value for key body. Each element in the list is a ConstrainedProperty.
By calling setMaxSize() we are just adding another ConstrainedProperty to the list of constraints.
I would like to show a series of financial transactions in a TableView.
Each Transaction consists of a Date, a Description and an Amount.
I can make this work using bindings if I treat all the cells as Text using the example shown in a reply to another question. This allows in cell editing which is my goal.
But I can't get it to work on the date and amount columns, I think I need a separate cell factory for each cell type and a possibly a separate updateItem method but I'm stuck.
Any pointers to an example or suggestions would be helpful.
You may want to check out the DataFX project at:
http://www.javafxdata.org/
and specifically the cell factories like:
http://www.javafxdata.org/javadoc/org/javafxdata/control/cell/TextFieldCellFactory.html
DataFX contains custom cell factories for several data types, tables, lists and tree views. Assuming that for example your amount has a double type, you could write something similar like that in a subclass of TableColum
(replace ??? by the class name of the class that represents a row in your table):
setCellFactory(TextFieldCellFactory.<???, Number>forTableColumn(new Callback<String,Number>(){
#Override
public Number call(String newValueStr) {
double newValue = Double.parseDouble(newValueStr);
return newValue;
}));
setOnEditCommit(new EventHandler<CellEditEvent<???, Number>>() {
#Override
public void handle(CellEditEvent<???, Number> t) {
double newValue = t.getNewValue().doubleValue();
// do something with the double value the user entered here
}
});
}
}
I hope that at least gives you some direction. I have left out Exception handling for clarity.
Is there a way to group domain object constraints? Something like this:
static constraints = {
personalDetails {
firstName(nullable: false)
}
address {
street(nullable: false)
}
}
Rich domain plugin does this for NON-domain objects... I want to do this FOR domain objects.
As i know this format does not supported by Grails. May be shared constraints will help you.