Skip to content

Commit 9067e89

Browse files
committed
Added on the fly validation.
TODO fix the problem with calling bind() on the verifying() and map() result.
1 parent 2c9ee17 commit 9067e89

13 files changed

+218
-36
lines changed

app/src/main/java/ext/sample/SimpleFormFragment.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,21 @@ public void onViewCreated(View view, Bundle savedInstanceState) {
4747
super.onViewCreated(view, savedInstanceState);
4848

4949
FormBuilder builder = new FormBuilder();
50+
5051
builder.addViewAndInput((EditText) view.findViewById(R.id.username), new StringInput("username", 6, 32));
52+
5153
builder.addView("birthday_day", (EditText) view.findViewById(R.id.birthday_day));
5254
builder.addView("birthday_month", (EditText) view.findViewById(R.id.birthday_month));
5355
builder.addView("birthday_year", (EditText) view.findViewById(R.id.birthday_year));
56+
5457
builder.addInput(new DateMultiInput("birthday").verifying(new Condition<DateTime>() {
5558
@Override
5659
public boolean verify(DateTime value) {
5760
// at least 18
5861
return value.isBefore(DateTime.now().minusYears(18));
5962
}
6063
}, R.string.birthday_error));
64+
6165
builder.addViewAndInput((RadioGroup) view.findViewById(R.id.gender),
6266
new StringInput("gender").map(new Mapping<String, Gender>() {
6367
@Override
@@ -78,8 +82,11 @@ public String unbind(Gender value) {
7882
return value.name();
7983
}
8084
}));
85+
8186
builder.addErrorHandler(new ViewErrorHandler((TextView) view.findViewById(R.id.birthday_error)), "birthday");
8287

88+
builder.setOnTheFlyValidation(true);
89+
8390
mForm = builder.build(new Mapping<Map<String, Object>, User>() {
8491
@Override
8592
public User bind(Map<String, Object> value) throws ValidationException {

lib/src/main/java/ext/extensions/forms/DateMultiInput.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package ext.extensions.forms;
22

3+
import android.util.Log;
4+
35
import org.joda.time.DateTime;
46
import org.joda.time.DateTimeZone;
57

@@ -10,6 +12,8 @@
1012

1113
import ext.R;
1214

15+
import static android.text.TextUtils.isEmpty;
16+
1317
/**
1418
* Created by evelina on 19/09/14.
1519
*/
@@ -68,7 +72,7 @@ public DateTime bind(Map<String, String> data) throws ValidationException {
6872
}
6973

7074
if (failures.size() > 0) {
71-
throw new ValidationException(failures.toArray(new ValidationFailure[failures.size()]));
75+
throw new ValidationException(failures);
7276
}
7377

7478
DateTime cal = new DateTime(year, month, 1, 0, 0, DateTimeZone.forTimeZone(TimeZone.getDefault()));
@@ -93,4 +97,19 @@ public DateTime bind(Map<String, String> data) throws ValidationException {
9397
public Map<String, String> unbind(DateTime value) {
9498
return super.unbind(value);
9599
}
100+
101+
@Override
102+
protected void onDataChanged(String key, String oldValue, String newValue, Map<String, String> partialData) throws ValidationException {
103+
Log.d("form", "DateMultiInput: " + key + " changed to " + newValue);
104+
if(key.equals(dayKey) || key.equals(monthKey) || key.equals(yearKey)){
105+
String day = partialData.get(dayKey);
106+
String month = partialData.get(monthKey);
107+
String year = partialData.get(yearKey);
108+
109+
if(!isEmpty(day) && !isEmpty(month) && !isEmpty(year)){
110+
// FIXME the bind should be done on the Input resulting from verifying() or map()
111+
bind(partialData);
112+
}
113+
}
114+
}
96115
}

lib/src/main/java/ext/extensions/forms/EditTextErrorHandler.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@ public EditTextErrorHandler(EditText view) {
1717
public void onError(ValidationFailure[] failures) {
1818
view.setError(failures[0].getMessage(view.getContext()));
1919
}
20+
21+
@Override
22+
public void reset() {
23+
view.setError(null);
24+
}
2025
}

lib/src/main/java/ext/extensions/forms/ErrorHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66
public interface ErrorHandler {
77

88
void onError(ValidationFailure[] failures);
9+
10+
void reset();
911
}

lib/src/main/java/ext/extensions/forms/Form.java

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,23 @@ public abstract class Form<T> {
1919
public final Input<?>[] inputs;
2020
public final Map<String, ViewPair<?>> views;
2121
public final Map<String, ErrorHandler[]> errorHandlers;
22+
public final boolean onTheFlyValidation;
23+
24+
protected Form(Input<?>[] inputs,
25+
Map<String, ViewPair<?>> views,
26+
Map<String, ErrorHandler[]> errorHandlers,
27+
boolean onTheFlyValidation) {
2228

23-
protected Form(Input<?>[] inputs, Map<String, ViewPair<?>> views, Map<String, ErrorHandler[]> errorHandlers) {
2429
this.inputs = inputs;
2530
this.views = views;
2631
this.errorHandlers = errorHandlers;
27-
}
28-
29-
public T bind() throws ValidationException {
30-
Map<String, String> data = new HashMap<>();
31-
for (Entry<String, ViewPair<?>> view : views.entrySet()) {
32-
data.put(view.getKey(), view.getValue().get());
33-
}
34-
try {
35-
return bind(data);
36-
} catch (ValidationException e) {
37-
onValidationError(e);
38-
throw e;
32+
this.onTheFlyValidation = onTheFlyValidation;
33+
if(onTheFlyValidation){
34+
addOnTheFlyValidationListeners();
3935
}
4036
}
4137

38+
4239
private void onValidationError(ValidationException e) {
4340
Map<String, List<ValidationFailure>> failureMap = new HashMap<>();
4441
for (ValidationFailure failure : e.failures) {
@@ -61,13 +58,73 @@ private void onValidationError(ValidationException e) {
6158
}
6259
}
6360

61+
private void resetValidationErrors() {
62+
if (!errorHandlers.isEmpty()) {
63+
for (ErrorHandler[] handlers : errorHandlers.values()) {
64+
for (ErrorHandler handler : handlers) {
65+
handler.reset();
66+
}
67+
}
68+
}
69+
}
70+
71+
private void addOnTheFlyValidationListeners() {
72+
for (final Entry<String, ViewPair<?>> entry : views.entrySet()) {
73+
entry.getValue().setOnDataChangeListener(new OnDataChangeListener() {
74+
@Override
75+
public void onDataChanged(String oldValue, String newValue) {
76+
Form.this.onDataChanged(entry.getKey(), oldValue, newValue);
77+
}
78+
});
79+
}
80+
}
81+
82+
protected void onDataChanged(final String key, final String oldValue, final String newValue) {
83+
Map<String, String> partialData = extractData();
84+
List<ValidationFailure> failures = new ArrayList<>();
85+
for (Input input : inputs) {
86+
try {
87+
input.onDataChanged(key, oldValue, newValue, partialData);
88+
} catch (ValidationException e) {
89+
failures.addAll(Arrays.asList(e.failures));
90+
}
91+
}
92+
if(!failures.isEmpty()) {
93+
onValidationError(new ValidationException(failures));
94+
}
95+
}
96+
97+
private Map<String, String> extractData() {
98+
Map<String, String> data = new HashMap<>();
99+
for (Entry<String, ViewPair<?>> view : views.entrySet()) {
100+
data.put(view.getKey(), view.getValue().get());
101+
}
102+
return data;
103+
}
104+
105+
public T bind() throws ValidationException {
106+
Map<String, String> data = extractData();
107+
resetValidationErrors();
108+
109+
try {
110+
return bind(data);
111+
} catch (ValidationException e) {
112+
onValidationError(e);
113+
throw e;
114+
}
115+
}
116+
64117
public abstract T bind(Map<String, String> data) throws ValidationException;
65118
}
66119

67120
class MapForm extends Form<Map<String, Object>> {
68121

69-
MapForm(Input<?>[] inputs, Map<String, ViewPair<?>> views, Map<String, ErrorHandler[]> errorHandlers) {
70-
super(inputs, views, errorHandlers);
122+
MapForm(Input<?>[] inputs,
123+
Map<String, ViewPair<?>> views,
124+
Map<String, ErrorHandler[]> errorHandlers,
125+
boolean onTheFlyValidation) {
126+
127+
super(inputs, views, errorHandlers, onTheFlyValidation);
71128
}
72129

73130
@Override
@@ -82,18 +139,23 @@ public Map<String, Object> bind(Map<String, String> data) throws ValidationExcep
82139
}
83140
}
84141
if (failures.isEmpty()) return values;
85-
else throw new ValidationException(failures.toArray(new ValidationFailure[0]));
142+
else throw new ValidationException(failures);
86143
}
87144
}
88145

89146
class ObjectForm<T> extends Form<T> {
90147
public final Mapping<Map<String, Object>, T> mapping;
91148
private final MapForm mMapForm;
92149

93-
ObjectForm(Mapping<Map<String, Object>, T> mapping, Input<?>[] inputs, Map<String, ViewPair<?>> views, Map<String, ErrorHandler[]> errorHandlers) {
94-
super(inputs, views, errorHandlers);
150+
ObjectForm(Mapping<Map<String, Object>, T> mapping,
151+
Input<?>[] inputs,
152+
Map<String, ViewPair<?>> views,
153+
Map<String, ErrorHandler[]> errorHandlers,
154+
boolean onTheFlyValidation) {
155+
156+
super(inputs, views, errorHandlers, onTheFlyValidation);
95157
this.mapping = mapping;
96-
mMapForm = new MapForm(inputs, views, errorHandlers);
158+
mMapForm = new MapForm(inputs, views, errorHandlers, onTheFlyValidation);
97159
}
98160

99161
@Override
@@ -114,4 +176,8 @@ class ViewPair<V extends View> {
114176
public String get() {
115177
return extractor.get(view);
116178
}
179+
180+
public void setOnDataChangeListener(OnDataChangeListener onDataChangeListener) {
181+
extractor.addChangeListener(view, onDataChangeListener);
182+
}
117183
}

lib/src/main/java/ext/extensions/forms/FormBuilder.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class FormBuilder {
2424
private Set<Input<?>> mInputs = new HashSet<>();
2525
private Map<String, ViewPair<?>> mViewMap = new HashMap<>();
2626
private Map<String, List<ErrorHandler>> mErrorHandlers = new HashMap<>();
27+
private boolean mOnTheFlyValidation = false;
2728

2829
private Input<?>[] toArray() {
2930
return mInputs.toArray(new Input<?>[mInputs.size()]);
@@ -106,11 +107,16 @@ public synchronized FormBuilder addErrorHandler(ErrorHandler errorHandler, Strin
106107
return this;
107108
}
108109

110+
public synchronized FormBuilder setOnTheFlyValidation(boolean onTheFlyValidation) {
111+
mOnTheFlyValidation = onTheFlyValidation;
112+
return this;
113+
}
114+
109115
public synchronized Form<Map<String, Object>> build() {
110-
return new MapForm(toArray(), mViewMap, toErrorHandlerArray());
116+
return new MapForm(toArray(), mViewMap, toErrorHandlerArray(), mOnTheFlyValidation);
111117
}
112118

113119
public synchronized <T> Form<T> build(Mapping<Map<String, Object>, T> mapping) {
114-
return new ObjectForm(mapping, toArray(), mViewMap, toErrorHandlerArray());
120+
return new ObjectForm(mapping, toArray(), mViewMap, toErrorHandlerArray(), mOnTheFlyValidation);
115121
}
116122
}

lib/src/main/java/ext/extensions/forms/Input.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package ext.extensions.forms;
22

3+
import android.util.Log;
4+
35
import java.util.HashMap;
46
import java.util.Map;
57

@@ -14,21 +16,21 @@ protected Input(String key) {
1416
this.key = key;
1517
}
1618

17-
public T bind(Map<String, String> data) throws ValidationException{
19+
public T bind(Map<String, String> data) throws ValidationException {
1820
return bindSingle(data.get(key));
1921
}
2022

21-
protected T bindSingle(String data) throws ValidationException{
23+
protected T bindSingle(String data) throws ValidationException {
2224
return null;
2325
}
2426

25-
public Map<String, String> unbind(T value){
26-
Map<String,String> data = new HashMap<>();
27+
public Map<String, String> unbind(T value) {
28+
Map<String, String> data = new HashMap<>();
2729
data.put(key, unbindSingle(value));
2830
return data;
2931
}
3032

31-
protected String unbindSingle(T value){
33+
protected String unbindSingle(T value) {
3234
return null;
3335
}
3436

@@ -40,7 +42,7 @@ public Q bind(Map<String, String> data) throws ValidationException {
4042
}
4143

4244
@Override
43-
public Map<String,String> unbind(Q value) {
45+
public Map<String, String> unbind(Q value) {
4446
return Input.this.unbind(mapping.unbind(value));
4547
}
4648

@@ -53,20 +55,25 @@ protected Q bindSingle(String data) throws ValidationException {
5355
protected String unbindSingle(Q value) {
5456
return null;
5557
}
58+
59+
@Override
60+
protected void onDataChanged(String key, String oldValue, String newValue, Map<String, String> partialData) throws ValidationException {
61+
Input.this.onDataChanged(key, oldValue, newValue, partialData);
62+
}
5663
};
5764
}
5865

5966
public Input<T> verifying(final Condition<T> condition, final int errorMessageId) {
6067
return new Input<T>(key) {
6168
@Override
62-
public T bind(Map<String,String> data) throws ValidationException {
69+
public T bind(Map<String, String> data) throws ValidationException {
6370
T v = Input.this.bind(data);
6471
if (condition.verify(v)) return v;
6572
else throw new ValidationException(key, errorMessageId);
6673
}
6774

6875
@Override
69-
public Map<String,String> unbind(T value) {
76+
public Map<String, String> unbind(T value) {
7077
return Input.this.unbind(value);
7178
}
7279

@@ -79,9 +86,22 @@ protected T bindSingle(String data) throws ValidationException {
7986
protected String unbindSingle(T value) {
8087
return null;
8188
}
89+
90+
@Override
91+
protected void onDataChanged(String key, String oldValue, String newValue, Map<String, String> partialData) throws ValidationException {
92+
Input.this.onDataChanged(key, oldValue, newValue, partialData);
93+
}
8294
};
8395
}
8496

97+
protected void onDataChanged(String key, String oldValue, String newValue, Map<String, String> partialData) throws ValidationException {
98+
// default implementation where the input listens for its own change
99+
Log.d("form", "Input: " + key + " changed to " + newValue);
100+
if (this.key.equals(key)) {
101+
bind(partialData);
102+
}
103+
}
104+
85105
@Override
86106
public boolean equals(Object o) {
87107
if (this == o) return true;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package ext.extensions.forms;
2+
3+
/**
4+
* Created by evelina on 21/09/14.
5+
*/
6+
public interface OnDataChangeListener {
7+
8+
void onDataChanged(String oldValue, String newValue);
9+
}

0 commit comments

Comments
 (0)