How to change validation of a control after it has been initiated? - validation

I am wondering how can I dynamically change Validation on a field. I have control group, which has 5 fields. For this, I want to use custom validator on this control group, to check if any of the inputs are not empty. If any of those are empty, I want to set validation of all of the fields within this control group to be required.
To do this, I need to dynamically change validation of each control to be required, after I verify if any of the inputs are empty. I got the first part - but I still cant figure out how to change validation of a control after it has been initiated. Any ideas?
What I am doing at the moment, is I put a validator on the group like this:
this._formBuilder.group({....}, {validator: customValidator})

You can use a custom validator that changes behavior depending on a value
class MyComponent {
constructor(fb:FormBuilder) {
this.form = fb.group({
c1: ['', (c) => this.myValidator(c)],
...
});
}
someState = true;
myValidator(c:Control) {
if(this.someState && control.value ....) {
}
}
}
This way the validator can for example access the status of the current component. You can also move the validator to another class and pass the method reference of that class to the validator parameter and update properties of this class to change the behavior of the validator.
class MyValidator {
someState = true;
validate(c:Control) {
if(this.someState && control.value ....) {
}
}
}
class MyComponent {
myValidator = new MyValidator();
constructor(fb:FormBuilder) {
this.form = fb.group({
c1: ['', this.myValidator.validate.bind(this.myValidator)],
...
});
}
onSomeEvent() {
this.myValidator.someState = !this.myValidator.someState;
this.form.control.c1.updateValueAndValidity();
}
}

Related

Material Datepicker change month view event

is there any way how to detect that calendar has new month view is loaded, when "yellow buttons" (see screenshot) are used?
You can listen for click events of those buttons on the calendar using Renderer2.
Code from this blog post.
constructor( private renderer: Renderer2g) { }
ngAfterViewInit() {
let buttons = document.querySelectorAll('mat-calendar .mat-calendar-previous-button,' +
'mat-calendar .mat-calendar-next-button');
if (buttons) {
Array.from(buttons).forEach(button => {
this.renderer.listen(button, "click", () => {
console.log("Month changed");
});
})
}
}
You can just use your own header component with whatever buttons and events your want:
<mat-calendar [headerComponent]="headerComponent"></mat-calendar>
And declare a variable headerComponent in the .ts:
public headerComponent = MyCoolHeaderComponent;
You will have to provide your own UI and functionality for the header (in your MyCoolHeaderComponent class).
MatDatepicker component uses DateAdapter service to handle dates, and when you click on next or previous button, a handler runs on MatCalendarHeader component and this handler calls addCalendarMonths method on DateAdapter (if current view is month) so if you patch this function on DateAdapter service TEMPORARILY, you can do anything you like, for example:
in parent component
origAddCalendarMonths: any
constructor(#Optional() private _dateAdapter: DateAdapter<any>) { }
onOpenedStream() {
this.origAddCalendarMonths = this._dateAdapter.addCalendarMonths
this._dateAdapter.addCalendarMonths = (...args) => {
console.log('!!!')
// your logic
return this.origAddCalendarMonths.apply(this._dateAdapter, args)
}
}
onClosedStream() {
this._dateAdapter.addCalendarMonths = this.origAddCalendarMonths
}
ngOnDestroy() {
this.onClosedStream()
}
or if you use MatCalendar instead, you can provide some extended class from DateAdapter in parent component providers to provide that class as DateAdapter for all child of this component (NodeInjector)

Is there a way to pass extra parameters to Handsontable validator functions?

I am using Handsontable v0.35.1 which is the latest version at the time of posting this question. It is used as part of Angular 5 (Typescript) component and view.
For each cell in table, I attach a custom validator as per guidelines in official documentation . Code looks something like this:
class ValidationService {
static myCustomColumnAValidator(value, callback) {
var contextObject = this;
//... validation logic
}
static myCustomColumnBValidator(value, callback) {
var contextObject = this;
//... validation logic
}
}
var hot = new Handsontable(document.getElementById('myTableContainer'), {
data: [ { 'ColumnA': 'Data'}, { 'ColumbB' : 'Data' } }],
columns: [
{
data: 'ColumnA',
validator: ValidationService.myCustomColumnAValidator
},
{
data: 'ColumnA',
validator: ValidationService.myCustomColumnBValidator
}
]
});
Question is, can I pass some extra parameters to the custom validator functions (myCustomColumnAValidator and myCustomColumnBValidator) apart from the value and callback function reference? I need some extra parameters as part of validation logic.
Also note that I had to mark the custom validator functions in ValidationService as static because the context object this is overridden to be the ColumnSettings object when Handsontable calls the validation function. If this was not the case, I could initialize the ValidationService with some member variables via its constructor and use in non-static version of the same validation function using this which would refer to the ValidationService instance. But this does not seem to be possible because Handsontable overrides the "this" context object, which I can see by stepping through the Handsontable code which uses ValidationFunction.call(contextObject, value, callback) mechanism.
Any pointers will be greatly appreciated. Thanks!
Try this way,
columns: [
{
data: 'ColumnA',
validator: (value, callback, extraData = this.something) => {
console.log(extraData);
}
}
]
Pass whatever you want here
columns: [
{
data: 'ColumnA',
validator: ValidationService.myCustomColumnAValidator,
customParameter: 10
}
]
And you can access it like so this.customParameter in the validator.
Using Typescript, I was able to do the following in order to pass an additional parameter to the Handsontable column validator function so the validator can be dynamic based on this parameter. In my case, the parameter was an entire object that had many properties I needed to evaluate:
columns: [
{
data: 'ColumnA',
validator: (value, callback) => {
return this.yourCustomValidatorFunction(value, callback, yourCustomParameterHere)
}
}
]

How do I tell ReactiveUI to update bindings (i.e. RaiseCanExecuteChanged())?

How do I tell ReactiveUI to update bindings?
Normally, I would do something like this:
string _instructorNameInput;
public string InstructorNameInput
{
get { return _instructorNameInput; }
set
{
this.RaiseAndSetIfChanged(ref _instructorNameInput, value);
Submit.RaiseCanExecuteChanged();
}
}
However, the following isn't supported:
Submit.RaiseCanExecuteChanged();
As a result, how can I force bindings to update based on the CanExecute predicate that my command relies on?
Updated:
public partial class FormViewModel : ReactiveObject
{
public FormViewModel()
{
Submit = ReactiveCommand.Create(this.WhenAnyValue(x => x.CanSubmit));
Submit.Subscribe(x => OnSubmit());
}
bool _canExecute;
public bool CanSubmit
{
get { return !GetUnsatisfied().Any(); }
set { this.RaiseAndSetIfChanged(ref _canExecute, value); } // Need to update view based on command.CanExecute state change
}
void OnSubmit()
{
var rosterInfo = new RosterInfo(new Course(CourseInput.Name),
new Instructor(InstructorNameInput, InstructorIdInput));
var repository = GetRepository();
repository.AddCourseInfo(rosterInfo);
Publish(REQUEST_NAVIGATION_TO_SUBMITION_CONFIRMATION, rosterInfo);
}
ObservableCollection<RequiredField> GetUnsatisfied()
{
RequiredFields.Clear();
RequiredFields = Review();
return RequiredFields;
}
}
Multiple issues:
Have a read at the fundamentals on ReactiveObject, in particular how "Read-Write Properties" are written.
In your case, this.WhenAnyValue(x => x.CanSubmit) will trigger a refresh on the command whenever the property CanSubmit changes, but this one never does, because you never call the setter (and the getter has an incorrect impl).
Currently, your method GetUnsatisfied() has "polling" semantics, which mean you need something to trigger this method to update your command. This isn't reactive at all, you should instead bind/listen to updates.
If there's no way for you to make your Review() logic reactive, then you may do something like:
var canExec = Observable.Timer(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
.Select(_ => !GetUnsatisfied().Any());
Submit = ReactiveCommand.Create(canExec);
Submit.Subscribe(x => OnSubmit());
Basically, having a timer to do your polling.
(But I strongly suggest going further down the reactive way)

Grails: Custom constraint on class made Validateable not working

I have a class Report made validateable by adding it to the grails.validateable.classes property of Config.groovy:
class Report {
/* fields */
Product product
static constraints = {
product nullable: true, validator: { val ->
if(val && val.quantity > 0) {
return (val.code && val.locations.size() > 0)
}
}
}
}
class Product {
BigDecimal quantity
String code
List<Location> locations
}
With that, I try validating a Report by calling report.validate() however validate always returns true. I tried making the custom validator:
...
product validator: { val ->
return false
}
so that calling validate on a Report always returns false but it still returns true making the instance valid. As far as I can tell, custom validators on classes made Validateable are not called when calling validate.
Is this a known bug? I can't find a reference anywhere if it is. Can't I use custom validators for classes that are made Validateable? Or something is wrong with my code?
Edit: Fixed typo. Should be grails.validateable.classes instead of grails.validateable.class

How to have ASP.Net MVC 3.0 Checkboxfor as checked by default?

I want mt view to have the check box checked by default,
I tried something like this.
#Html.CheckBoxFor(model=>model.GenericsOK, new { id = ViewBag.GenericsOK, #checked = true })
and also
#Html.CheckBoxFor(model=>model.GenericsOK, new { id = ViewBag.GenericsOK, #checked = "checked"})
in both cased it give the below error.
String was not recognized as a valid Boolean.
My property is defined as this.
private bool _deafaultchecked = true;
[Display(Name = "Generics Ok")]
public bool GenericsOK
{
get { return _deafaultchecked; }
set { _deafaultchecked = value; }
}
any suggestions please?
Since i could not find a solution or this.
i got this done like this.
#Html.CheckBox("GenericsOK", true, new {id=ViewBag.GenericsOK, name="GenericsOK" })
this works for my requirement.
thanks for all who helped me.
In your controller's Create method (I presume), have you tried this?
public ActionResult Create()
{
return View(new YourModelClass { GenericsOk = true });
}
In the controller action where you create the model just set that field value to true.
For example
return View(new DriverCsvModel{SendEmails = true});
You should be using the state of the model, rather than forcing the UI into a checked state.
You will want to remove the #checked="checked" portion of the HTML attributes. If the viewmodel property is a boolean then it is unnecessary when you use the CheckBoxFor
In the default constructor for your model class, you can set the "GenericsOK" property to "True"

Resources