I have several TextFields and a submit button. I want my button to be disabled unless all the textfields have been validated. Below is my code for the BooleanBindings:
BooleanBinding firstNameValidation, middleNameValidation, lastNameValidation,
usernameValidation, passwordValidation, retypePasswordValidation, emailValidation, phoneNumberValidation;
firstNameValidation = Bindings.createBooleanBinding(()->{
if(employee.setFirstName(txtFirstName.getText()) == 0){
piFirstName.setProgress(100);
return true;
} else {
piFirstName.setProgress(0);
return false;
}
}, txtFirstName.textProperty());
middleNameValidation = Bindings.createBooleanBinding(()->{
if(employee.setMiddleName(txtMiddleName.getText()) == 0){
piMiddleName.setProgress(100);
return true;
} else {
piMiddleName.setProgress(0);
return false;
}
}, txtMiddleName.textProperty());
..and so on.
This is how I try to bind the disableProperty of the submit button to the BooleanBindings:
btnSetPermissions.disableProperty().bind(firstNameValidation.not().or(middleNameValidation.not()).or(lastNameValidation.not())
.or(usernameValidation.not()).or(passwordValidation.not()).or(retypePasswordValidation.not())
.or(emailValidation.not()).or(phoneNumberValidation.not()));
It works, but if the first condition returns false, it doesn't check the rest of the conditions because of the OR-operation.
I can't seem to find a solution. Any help would be much appreciated.
You're mixing your functionality too much. Separate it out into constituent components:
firstNameValidation = Bindings.createBooleanBinding(() -> {
String firstName = txtFirstName.getText();
if (/* firstName is valid */) {
return true ;
} else {
return false ;
}
}, txtFirstName.textProperty());
employee.firstNameProperty().bind(txtFirstName.textProperty());
// if you want employee.firstName to change only if the name is valid,
// use a listener here instead of the binding above:
txtFirstName.textProperty().addListener((obs, oldText, newText) -> {
if (firstNameValidation.get()) {
employee.setFirstName(newText);
}
});
piFirstName.bind(Bindings.when(firstNameValidation).then(100).otherwise(0));
// similarly for other properties...
// then (this logic is equivalent to yours, but easier to read imho)
btnSetPermissions.disableProperty().bind(
(firstNameValidation.and(middleNameValidation)
.and(userNameValidation).and(passwordValidation)
.and(retypePasswordValidation).and(emailValidation)
.and(phoneNumberValidation)
).not());
Note you can, of course, reduce the code by moving anything repetitive to a method in the usual way:
private BooleanBinding createValidationBinding(
TextField field, Predicate<String> validationRule,
StringProperty boundValue, ProgressIndicator indicator) {
BooleanBinding validation = Bindings.createBooleanBinding(() -> {
String value = field.getText();
return validationRule.test(value);
}, field.textProperty());
field.textProperty().addListener((obs, oldText, newText) -> {
if (binding.get()) {
boundValue.set(newText);
}
});
indicator.progressProperty().bind(Bindings.when(validation).then(100).otherwise(0));
return validation ;
}
Then you can do
firstNameValidation = createValidationBinding(txtFirstName,
name -> /* boolean indicating if name is valid */,
employee.firstNameProperty(), piFirstName);
and similarly for the other fields.
Personally, this is what I do:
btnSetPermissions.disableProperty.bind(Bindings.createBooleanBinding(() -> {
if (!validateFirstName() ||
!validateMiddleName() /* and so on */)
return true;
else return false;
}, firstNameProperty(), middleNameProperty() /* and other properties*/ ));
public boolean validateFirstName() {
// Your validation logic
}
public boolean validateMiddleName() {
// Your validation logic
}
/* Other validation methods */
There is no need to create so many objects (those BindingExpression objects), and you can easily use conditional operators like || and && which will guarantee skipping of non-essential checks whenever possible.
Related
I am trying to refactor old SimpleFormController. I would like to replace getSuccessView() and gerFormView() calls with actual success view and form view Strings.
I went through https://spoon.gforge.inria.fr/first_transformation.html, it shows how to generate and add statements however I could not understand how to modify.
I have tried couple of things.
Replace statements with the getSuccessView() and getFormView() calls
public class SimpleFormControllerReplaceViewCall extends AbstractProcessor<CtMethod> {
MetaData meta;
String successView= "successView";
String formView = "formView";
public SimpleFormControllerReplaceViewCall(MetaData meta) {
this.meta = meta;
}
#Override
public boolean isToBeProcessed(CtMethod candidate) {
if(candidate.getBody() == null) { //Ignore abstract methods
return false;
}
String sourceCode;
try {
sourceCode = candidate.getBody()
.getOriginalSourceFragment()
.getSourceCode();
} catch (Exception e) {
return false;
}
return sourceCode.contains(getViewFunctionName(successView))
|| sourceCode.contains(getViewFunctionName(formView));
}
#Override
public void process(CtMethod method) {
Node beanNode = getBeanNode(method);
CtBlock<Object> body = getFactory().createBlock();
method.getBody().getStatements()
.stream()
.map(s -> {
Optional<String> sourceCode = getStatementSourceCode(s);
if(!sourceCode.isPresent()) {
return s.clone(); // Clone required to handle runtime error for trying attach a node to two parents
} else {
System.out.println("Modifying: " + method.getSignature());
String code = sourceCode.get();
code = replaceViewCalls(beanNode, code, successView);
code = replaceViewCalls(beanNode, code, formView);
return getFactory().createCodeSnippetStatement(code);
}
}).forEach(body::addStatement);
method.setBody(body);
}
private Optional<String> getStatementSourceCode(CtStatement s) {
String sourceCode = null;
try {
sourceCode = s.getOriginalSourceFragment()
.getSourceCode();
} catch (Exception e) {}
System.out.println(sourceCode);
if (sourceCode != null &&
(sourceCode.contains(getViewFunctionName(successView))
|| sourceCode.contains(getViewFunctionName(formView)))) {
sourceCode = sourceCode.trim();
if(sourceCode.endsWith(";"))
sourceCode = sourceCode.substring(0, sourceCode.length()-1);
return Optional.of(sourceCode);
} else {
return Optional.empty();
}
}
public String replaceViewCalls(Node beanNode, String code, String viewType) {
String getViewFunctionName = getViewFunctionName(viewType);
if (!code.contains(getViewFunctionName)) {
return code;
}
String view = AppUtil.getSpringBeanPropertyValue(beanNode, viewType);
return code.replaceAll(getViewFunctionName + "\\(\\)", String.format("\"%s\"", view));
}
public Node getBeanNode(CtMethod method) {
String qualifiedName = method.getParent(CtClass.class).getQualifiedName();
return meta.getFullyQualifiedNameToNodeMap().get(qualifiedName);
}
private String getViewFunctionName(String viewType) {
return "get" + viewType.substring(0, 1).toUpperCase() + viewType.substring(1);
}
}
This however adds unwanted at end of blocks if() {... }; This creates syntax errors when if {} else {} blocks contain return statement(s). Auto import is turned on and imports are not added when there is more one class with same name (e.g., Map is present in classpath from few libraries) - this is consistent with the document. Can this be avoided when refactoring code? Original java file has correct imports.
Another approach I tried is to directly manipulate the body as a whole.
#Override
public void process(CtMethod method) {
String code = method.getBody()
.getOriginalSourceFragment()
.getSourceCode();
Node beanNode = getBeanNode(method);
code = replaceViewCalls(beanNode, code, successView);
code = replaceViewCalls(beanNode, code, formView);
CtCodeSnippetStatement codeStatement = getFactory().createCodeSnippetStatement(code);
method.setBody(codeStatement);
}
this still has same auto import issue as first one. Apart from that it adds redundant curly braces, for examples
void method() { x=y;}
will become
void method() { {x=y;} }
That that will be pretty printed ofcourse.
Also javadocs for getOriginalSourceFragment() also has below warning
Warning: this is a advanced method which cannot be considered as part
of the stable API
One more thing I thought of doing is creating pattern for each type of usage of getSuccessView() like
viewName = getSuccessView();
return getSuccessView();
return ModelAndView(getSuccessView(), map); etc, however for that I will have to write a whole bunch of processors / templates.
Since it is simple replacement, easiest is do something like below
//Walk over all files and execute
Files.lines(Paths.get("/path/to/java/file"))
.map(l -> l.replaceAll("getSuccessView\\(\\)", "actualViewNameWithEscapedQuotes"))
.map(l -> l.replaceAll("getFormView\\(\\)", "actualViewNameWithEscapedQuotes"))
.forEach(l -> {
//write to file
});
Since I can avoid text manipulation with the help of spoon for things like changing modifiers, annotations, method name, annotations etc, I am hoping there should be a better way to modify the method body.
You should treat the processor input as an abstract syntax tree instead of a string:
public class SimpleFormControllerReplaceViewCall extends AbstractProcessor<CtMethod<?>> {
#Override
public boolean isToBeProcessed(CtMethod candidate) {
if(candidate.isAbstract()) { //Ignore abstract methods
return false;
}
return !candidate.filterChildren((CtInvocation i)->
i.getExecutable().getSimpleName().equals("getSuccessView")
|| i.getExecutable().getSimpleName().equals("getFormView")).list().isEmpty();
}
#Override
public void process(CtMethod<?> ctMethod) {
Launcher launcher = new Launcher();
CodeFactory factory = launcher.createFactory().Code();
List<CtInvocation> invocations = ctMethod.filterChildren((CtInvocation i)->
i.getExecutable().getSimpleName().equals("getSuccessView")
|| i.getExecutable().getSimpleName().equals("getFormView")).list();
for(CtInvocation i : invocations) {
if(i.getExecutable().getSimpleName().equals("getSuccessView")) {
i.replace(factory.createLiteral("successView"));
} else {
i.replace(factory.createLiteral("formView"));
}
}
}
}
Here the CtMethod AST is traversed in search for CtInvocation elements with the specified properties. The found elements are then replaced with new string literal elements.
I tried disable button when some integer value != 1
For example (my idOrder is IntegerProperty)
refreshButton.disableProperty().bind(new BooleanBinding() {
#Override
protected boolean computeValue() {
return currentOrder.getIdOrder() != 1;
}
});
And it's works. But when I changed value on 1 (currentOrder.setIdOrder(1)) button is still disabled.
What I doing wrong?
You've created a BooleanBinding but haven't configured it to observe anything, thus it will never be notified of your property changing. You need to invoke BooleanBinding#bind(Observable...) during instantiation. For example:
refreshButton.disableProperty().bind(new BooleanBinding() {
{
bind(currentOrder.idOrderProperty());
}
#Override protected boolean computeValue() {
return currentOrder.getIdOrder() != 1;
}
#Override public void dispose() {
// for a proper implementation, we need this as well
unbind(currentOrder.idOrderProperty());
}
});
That said, the above can be simplified with Bindings#createBooleanBinding(Callable,Observable...):
refreshButton.disableProperty()
.bind(Bindings.createBooleanBinding(
() -> currentOrder.getIdOrder() != 1, currentOrder.idOrderProperty()));
But even that can be simplified further with one of the following:
Bindings#notEqual(int,ObservableNumberValue):
refreshButton.disableProperty().bind(Bindings.notEqual(1, currentOrder.idOrderProperty());
NumberExpresion#isNotEqualTo(int):
refreshButton.disableProperty().bind(currentOrder.idOrderProperty().isNotEqualTo(1));
I have an enum with values VALID and INVALID, which have a boolean property associated with them. I would like to get the enum value based on a boolean value I provide.
If it is true I should get VALID, if it is false I should get INVALID. I would like to do so in a getter method like the below, based on the value of the member variable
public boolean getCardValidityStatus() {
return CardValidationStatus status = CardValidationStatus(this.mCardValidityStatus));
}
My code:
private enum CardValidationStatus {
VALID(true),
INVALID(false);
private boolean isValid;
CardValidationStatus(boolean isValid) {
this.isValid = isValid;
}
public boolean getValidityStatus() {
return this.isValid;
}
}
You're able to achieve that using a static lookup method in the enum itself:
private enum CardValidationStatus {
VALID(true),
INVALID(false);
//...
public static CardValidationStatus forBoolean(boolean status) {
//this is simplistic given that it's a boolean-based lookup
//but it can get complex, such as using a loop...
return status ? VALID : INVALID;
}
}
And the appropriate status can be retrieved using:
public CardValidationStatus getCardValidityStatus() {
return CardValidationStatus.forBoolean(this.mCardValidityStatus));
}
I would add a parse method to your enum, which takes the boolean, iterates over all the values and returns the one that matches, for example:
public CardValidationStatus parse(boolean isValid) {
for (CardValidationStatus cardValidationStatus : CardValidationStatus.values()) {
if (cardValidationStatus.getValidityStatus() == isValid) {
return cardValidationStatus;
}
}
throw new IllegalArgumentException();
}
#ernest_k solution made this work, but I think that's not reliable solution.
You should always do code which is independent.
Because his solution is hardcoded. What if values of VALID & INVALID are changed. Will you change your forBoolean logics also?
Because he did not check what the Enum fields are holding inside it.
Reliable solution will be #DaveyDaveDave answer. This will also work when you have many status with VALID & INVAlID.
private enum CardValidationStatus {
VALID(true),
INVALID(false);
//...
public CardValidationStatus forBoolean(boolean isValid) {
for (CardValidationStatus cardValidationStatus : CardValidationStatus.values()) {
if (cardValidationStatus.getValidityStatus() == isValid) {
return cardValidationStatus;
}
}
throw new IllegalArgumentException();
}
}
Suggestion (Easiest way I think)
Why are you making Enum just for storing 2 boolean values?
Just make static boolean named by VALID & INVALID.
public static final boolean CARD_STATUS_VALID = true;
public static final boolean CARD_STATUS_INVALID = false;
if(cardStatus == CARD_STATUS_VALID){
// todo
}
Is there a way to do a low level bind but also be able to still do setDisable(ture/false) to a controller?
For example:
HBoxSomeBox.disableProperty().bind(new BooleanBinding() {
{
bind(someIntValue);
}
#Override
protected boolean computeValue() {
return someIntValue >=2 ;
}
});
And somewhere else in the code to do HBoxSomeBox.setDisable(false).
Currently when I try to do that it throws an exception:
java.lang.RuntimeException: HBox.disable : A bound value cannot be set.
So is there another way to have a bound controller but also to be able to set it?
From the comments, you appear to want to disable your control anytime the value of someIntValue is at least two, or under other circumstances "dictated by the view". You could either create a BooleanProperty representing those other circumstances, and use it in the binding:
IntegerProperty someIntProperty = ... ;
BooleanProperty forceDisable = new SimpleBooleanProperty();
hboxSomeHBox.disableProperty().bind(new BooleanBinding() {
{
bind(someIntValue, forceDisable);
}
#Override
public boolean computeValue() {
return someIntValue.get() >= 2 || forceDisable.get() ;
}
}
or, more succinctly,
BooleanProperty forceDisable = new SimpleBooleanProperty();
hboxSomeHBox.disableProperty().bind(someIntValue.greaterThanOrEqualTo(2).or(forceDisable));
Then calling forceDisable.set(true); will disable the control.
You can also achieve this simply with a listener:
someIntValue.addListener((obs, oldValue, newValue) -> {
if (newValue.intValue() >= 2) {
hboxSomeHBox.setDisable(true);
}
});
Since the disable property is not bound, you are free to set it in the usual way.
I was just creating this specific but I was a little confused on documenting this. Am just stuck on explaining what the last couple of lines do :
class MyVerifier extends InputVerifier {
public boolean verify(JComponent input) {
if (input==id) {
return validId();
}
else if (input==name) {
return validName();
}
return false;
}
public boolean validId() {
boolean status;
String theID = id.getText();
Pattern pattern = Pattern.compile("\\d{8}");
Matcher matcher = pattern.matcher(theID);
if (matcher.matches()) {
status = true;
}
else {
status = false;
}
return status;
}
public boolean validName() {
boolean status;
String theName = name.getText();
Pattern pattern = Pattern.compile("[A-za-z0-9 ]+");
Matcher matcher = pattern.matcher(theName);
if (matcher.matches()) {
status = true;
}
else {
status = false;
}
return status;
}
}
COULD YOU EXPLAIN THESE SPECIFIC LINES HERE ONE BY ONE ?
/**
* #param o the object corresponding to the user's selection
*/
#Override
public void tell(Object o) { -- Where has this come from ?
deptCode.setText(o.toString());
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == submit) {
MyVerifier test = new MyVerifier();
if (Staff.getStaff(id.getText()) == null && test.verify(id) &&
test.verify(name)) {
System.out.println("YAY");-- What is this doing
}
else if (!(Staff.getStaff(id.getText()) == null)) {
String errorMessage = "ID EXISTS: " + Staff.getStaff(id.getText()).toString(); -- What is this doing
JOptionPane.showMessageDialog(theFrame, errorMessage, "Error",
JOptionPane.WARNING_MESSAGE);-- What is this doing
}
else {
System.out.println("Woops.");
}
}
else if (e.getSource() == clear) {
id.setText(null);
deptCode.setText(null);
name.setText(null);
}
}
public static void main(String[] args) {
Registration test = new Registration();
}
}
Now that you understand what you're trying to accomplish with this program, start from a clean slate (using your first attempt as an example if necessary). It's often easier to start over than to fix a program.
It appears that your public void tell(Object o) method is setting a String with the value of the object passed. Because you haven't shown us what your using it for, though, it's impossible for us to know for sure. On the other hand, your other problems are fairly clear:
System.out.println("YAY");
It appears that Staff.getStaff(id.getText) is checking either a String or a text file for a list of names and id's. This statement prints "YAY" only if there hasn't previously been created a staff member with the provided id and name. But since you also haven't shown us where those variables are, this is only my best guess.
JOptionPane.showMessageDialog(theFrame, errorMessage, "Error", JOptionPane.WARNING_MESSAGE);
This displays a JOptionPane warning message if there is already a staff member with the given id or name. Obviously, you cannot create an account that someone else has, so this JOptionPane displays an error message if this is, indeed, the case.