How to validate inputs and prevent save actions using databinding in eclipse? - eclipse

I want to create input forms which validate user input and prevent the model from being saved with invalid data. I have been using databinding which works up to a point but my implementation is not as intuitive as I would like.
Imagine an input which contains '123' and the value must not be empty. The user deletes the characters one by one until empty. The databinding validator shows an error decoration.
However, if the user saves the form and reloads it, then a '1' is displayed in the field - i.e. the last valid input. The databinding does not transmit the invalid value into the model.
I have a ChangeListener but this is called before the databinding so at that point the invalid state has not been detected.
I would like the error to be displayed in the UI but the model remains valid (this is already so). Also, for as long as the UI contains errors, it should not be possible to save the model.
/**
* Bind a text control to a property in the view model
**/
protected Binding bindText(DataBindingContext ctx, Control control,
Object viewModel, String property, IValidator validator)
{
IObservableValue value = WidgetProperties.text(SWT.Modify).observe(
control);
IObservableValue modelValue = BeanProperties.value(
viewModel.getClass(), property).observe(viewModel);
Binding binding = ctx.bindValue(value, modelValue, getStrategy(validator), null);
binding.getTarget().addChangeListener(listener);
ControlDecorationSupport.create(binding, SWT.TOP | SWT.LEFT);
return binding;
}
private UpdateValueStrategy getStrategy(IValidator validator)
{
if (validator == null)
return null;
UpdateValueStrategy strategy = new UpdateValueStrategy();
strategy.setBeforeSetValidator(validator);
return strategy;
}
private IChangeListener listener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
// notify all form listeners that something has changed
}
};
/**
* Called by form owner to check if the form contains valid data e.g. before saving
**/
public boolean isValid()
{
System.out.println("isValid");
for (Object o : getDataContext().getValidationStatusProviders())
{
ValidationStatusProvider vsp = (ValidationStatusProvider) o;
IStatus status = (IStatus)vsp.getValidationStatus()
.getValue();
if (status.matches(IStatus.ERROR))
return false;
}
return true;
}

Your best bet is to steer clear of ChangeListeners - as you've discovered, their order of execution is either undefined or just not helpful in this case.
Instead, you want to stick with the 'observable' as opposed to 'listener' model for as long as possible. As already mentioned, create an AggregateValidationStatus to listen to the overall state of the DataBindingContext, which has a similar effect to your existing code.
Then you can either listen directly to that (as below) to affect the save ability, or you could even bind it to another bean.
IObservableValue statusValue = new AggregateValidationStatus(dbc, AggregateValidationStatus. MAX_SEVERITY);
statusValue.addListener(new IValueChangeListener() {
handleValueChange(ValueChangeEvent event) {
// change ability to save here...
}
});

You can use AggregateValidationStatus to observe the aggregate validation status:
IObservableValue value = new AggregateValidationStatus(bindContext.getBindings(),
AggregateValidationStatus.MAX_SEVERITY);
You can bind this to something which accepts an IStatus parameter and it will be called each time the validation status changes.

Related

How to refresh a FilteredItemsSelectionDialog

I have a FilteredItemsSelectionDialog shose underlying data model may change while the dialog is open. If there are structural changes, i.e. elements are added or removed, I am unable to tell the dialog to update its filtered list.
I tried calling refresh() and reloadCache() and also scheduleRefresh() (which essentially runs reloadCache() in a Job), but all methods re-use the elements that were initially added to the content provider.
What I think would solve the problem is a way to reset the content provider and have it call fillContentProvider() again. Then I could re-populate the content provider with the current state of the model. While preserving the current filter and selection, of course.
[BEGIN EDIT]
Based on Greg's answer here is what I also tried:
ItemsFilter overrideFilter;
#Override
protected ItemsFilter createFilter() {
if( overrideFilter != null ) {
return overrideFilter;
}
return new LaunchConfigItemsFilter();
}
public void forceRefresh() {
overrideFilter = new ItemsFilter() {
#Override
public boolean matchItem( Object item ) {
return false;
}
#Override
public boolean isConsistentItem( Object item ) {
return false;
}
#Override
public boolean equalsFilter( ItemsFilter filter ) {
return false;
}
#Override
public boolean isSubFilter( ItemsFilter filter ) {
return false;
}
};
applyFilter();
overrideFilter = null;
applyFilter();
}
forceRefresh() is called whenever the model structure changes. The intention is to first call applyFilter() with a filter that will never match the existing one and thus triggers fillContentProider() and then again call applyFilter() with the current filter to restore the elements that match.
But when an element is added and another is removed, the outcome is that the deleted element is still visible.
What I also tried is to let createFilter() return null for the first call to applyFilter(). This again leaves the deleted element visible.
Irrespective of whether a fitler was set or not before the elments were added and removed, at its best, the list is in a meaningful state only after I cleared and (re-)entered a filter.
[END EDIT]
To give some context, the dialog in question lists launch configurations, the code can be found here:
https://github.com/rherrmann/eclipse-extras/blob/master/com.codeaffine.extras.launch/src/com/codeaffine/extras/launch/internal/dialog/LaunchSelectionDialog.java
Does anyone know a way to force the FilteredItemsSelectionDialog to reset and refill its content provider?
If I read the code correctly calling applyFilter will rerun all the code to fill the table including calling fillContentProvider.
When checking if it needs to do anything applyFilter calls createFilter, this must return a filter and the filter's ItemsFilter.equalsFilter method must return false when compared with the previous filter.

How to save draft of form by erasing any fields that fail validation in MVC4

We need users to be able to save a draft of their form in MVC4.
We already implemented a "Save Draft" button which disables the
client side validation entirely and submits the form.
Now on the server side the plan is to ignore any form fields which
have validation errors. For example, if a zip code field has "abc"
then we'll ignore that field and save the rest. If any field are
required but not provided those will also be ignored.
We're trying to figure out an elegant way to do this in MVC4 via
IModelBinder.
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var result = base.BindModel(controllerContext, bindingContext);
if (!controllerContext.Controller.ViewData.ModelState.IsValid)
{
foreach( var state in controllerContext.Controller.ViewData.ModelState)
{
if( state.Value.Errors.Count > 0) {
var fieldName = state.Key;
// Clear original data field that has the error.
}
}
// Now redo the binding without the fields that presented a problem.
result = base.BindModel(controllerContext, bindingContext);
}
return result;
}
}
So the problem above is how to clear the specific field data so that it can re-run
the data binding and generate the model without the fields that cause validation errors?

Form fields are reset on validation error

I have a rather complex form in the way that the number of form fields is flexibel. In short, the model object is a TLabel (TranslationLabel) that contains a Map of values (translations). Language here is an enum so the idea is that the number of fields (text areas) for which a translation is given depends on the values in this enum.
This is my form (simplified):
public class TranslationEditForm extends Form {
private final static List<Language> LANGUAGES = newArrayList(Language.values());
public TranslationEditForm(String id, final TranslationLabelView label) {
super(id, new CompoundPropertyModel<TranslationLabelView>(label));
ListView<Language> textAreas = new ListView<Language>("translationRepeater", LANGUAGES) {
#Override
protected void populateItem(final ListItem<Language> itemLang) {
//loop through the languages and create 1 textarea per language
itemLang.add(new Label("language", itemLang.getModelObject().toString()));
Model<String> textModel = new Model<String>() {
#Override
public String getObject() {
//return the value for current language
return label.getValue(itemLang.getModelObject());
}
#Override
public void setObject(String object) {
//set the value for current language
label.getTranslations().put(itemLang.getModelObject(), object);
}
};
itemLang.add(new TextArea<String>("value", textModel).setRequired(true));
}
};
//add the repeater containing a textarea per language to the form
this.add(textAreas);
}
}
Now, it works fine, 1 text area is created per language and its value is also set nicely; even more when changed the model gets updated as intended.
If you submit the form after emptying a text area (so originally there was a value) then of course there is a validation error (required). Normal (wicket) behaviour would be that the invalid field is still empty but for some reason the original value is reset and I don't understand why.
If I override onError like this:
#Override
protected void onError() {
this.updateFormComponentModels();
}
then it is fine, the value of the field is set to the submitted value (empty) instead of the original value.
Any idea what is causing this? What is wicket failing to do because the way I've set up the form (because with a simple form/model this is working fine as are the wicket examples)?
Posted as answer, so the question can be marked as solved:
ListView does recreate all its items at render time. This means that the validation will be broken. Have a look at API doc of the ListView
Calling setReuseItems() on the ListView solves this.
Regards,
Bert

GWT RequestFactory + CellTable

Does anyone know for an example of GWT's CellTable using RequestFactory and that table is being edited? I would like to list objects in a table (each row is one object and each column is one property), be able to easily add new objects and edit. I know for Google's DynaTableRf example, but that one doesn't edit.
I searched Google and stackoverflow but wasn't able to find one. I got a bit confused with RF's context and than people also mentioned some "driver".
To demonstrate where I currently arrived, I attach code for one column:
// Create name column.
Column<PersonProxy, String> nameColumn = new Column<PersonProxy, String>(
new EditTextCell()) {
#Override
public String getValue(PersonProxy person) {
String ret = person.getName();
return ret != null ? ret : "";
}
};
nameColumn.setFieldUpdater(new FieldUpdater<PersonProxy, String>() {
#Override
public void update(int index, PersonProxy object, String value) {
PersonRequest req = FaceOrgFactory.getInstance().requestFactory().personRequest();
PersonProxy eObject = req.edit(object);
eObject.setName(value);
req.persist().using(eObject).fire();
}
});
and my code for data provider:
AsyncDataProvider<PersonProxy> personDataProvider = new AsyncDataProvider<PersonProxy>() {
#Override
protected void onRangeChanged(HasData<PersonProxy> display) {
final Range range = display.getVisibleRange();
fetch(range.getStart());
}
};
personDataProvider.addDataDisplay(personTable);
...
private void fetch(final int start) {
lastFetch = start;
requestFactory.personRequest().getPeople(start, numRows).fire(new Receiver<List<PersonProxy>>() {
#Override
public void onSuccess(List<PersonProxy> response) {
if (lastFetch != start){
return;
}
int responses = response.size();
if (start >= (personTable.getRowCount()-numRows)){
PersonProxy newP = requestFactory.personRequest().create(PersonProxy.class);
response.add(newP);
responses++;
}
personTable.setRowData(start, response);
personPager.setPageStart(start);
}
});
requestFactory.personRequest().countPersons().fire(new Receiver<Integer>() {
#Override
public void onSuccess(Integer response) {
personTable.setRowCount(response+1, true);
}
});
}
I try to insert last object a new empty object. And when user would fill it, I'd insert new one after it. But the code is not working. I says that user is "attempting" to edit a object previously edited by another RequestContext.
Dilemmas:
* am I creating too many context'es?
* how to properly insert new object into celltable, created on the client side?
* on fieldUpdater when I get an editable object - should I insert it back to table or forget about it?
Thanks for any help.
am I creating too many context'es?
Yes.
You should have one context per HTTP request (per fire()), and a context that is not fire()d is useless (only do that if you/the user change your/his mind and don't want to, e.g., save your/his changes).
You actually have only one context to remove here (see below).
Note that your approach of saving on each field change can lead to "race conditions", because a proxy can be edit()ed by at most one context at a time, and it remains attached to a context until the server responds (and once a context is fired, the proxy is frozen –read-only– also until the server responds).
(this is not true in all cases: when onConstraintViolation is called, the context and its proxies are unfrozen so you can "fix" the constraint violations and fire the context again; this should be safe because validation is done on the server-side before any service method is called).
how to properly insert new object into celltable, created on the client side?
Your code looks OK, except that you should create your proxy in the same context as the one you'll use to persist it.
on fieldUpdater when I get an editable object - should I insert it back to table or forget about it?
I'm not 100% certain but I think you should refresh the table (something like setRowData(index, Collections.singletonList(object)))
BTW, the driver people mention is probably the RequestFactoryEditorDriver from the Editor framework. It won't help you here (quite the contrary actually).

WizardForm in Spring 3

I found a code from Spring forums that seems to be an interesting way to implement wizardForms in Spring 3:
#RequestMapping(method = RequestMethod.POST)
public ModelAndView processSubmit(
#ModelAttribute("pet") Pet pet,
SessionStatus status) {
if (pet.getFieldOne() == null) {
//return the form that will set field one's value
return new ModelAndView( ... );
} else if (pet.getFieldTwo() == null) {
//return the form that will set field two's value
return new ModelAndView( ... );
} //and so on for all the other field that need to be set...
...
else {
//once the object has all necessary fields
//set and validated, then do what needs
//to be done to finish. Store object, end
//session, and return your success view.
this.clinic.storePet(pet);
status.setComplete();
return new ModelAndView( ... );
}
}
Can anyone tell me what the storing here means, and is this a good way?
If by "storing" you mean this.clinic.storePet(pet);, it's an action of saving the complete object in your database when wizard is finished, so that it's completely unrelated to wizard implementation.
The approach itself is a standard way to implement wizard forms in Spring 3 that replaces the deprectated AbstractWizardFormController.
Note that it also requires #SessionAttribute("pet") as a class-level annotation. This annotation makes Spring to store the corresponding model attribute in the session between requests, so that each form submission sets fields of the same object. When all fields are set and wizard is finished, object is saved to the database, and removed from the session by status.setComplete();.