Maybe I missunderstood JavaFX binding or there is a bug in SimpleStringProperty.
When I run this testcode my changed model value didn't get the new value. Test testBindingToModel fails. I thought my model should then be updated with the value of the TextField tf. But only the binding value of prop1Binding gets the value "test".
public class BindingTest {
private TextField tf;
private Model model;
private ModelBinding mb;
#Before
public void prepare() {
tf = new TextField();
model = new Model();
mb = new ModelBinding(model);
Bindings.bindBidirectional(tf.textProperty(), mb.prop1Binding);
}
#Test
public void testBindingToMB() {
tf.setText("test");
assertEquals(tf.getText(), mb.prop1Binding.get());
}
#Test
public void testBindingToModel() {
tf.setText("test");
assertEquals(tf.getText(), mb.prop1Binding.get());
assertEquals(tf.getText(), model.getProp1());
}
private static class ModelBinding {
private final StringProperty prop1Binding;
public ModelBinding(Model model) {
prop1Binding = new SimpleStringProperty(model, "prop1");
}
}
private static class Model {
private String prop1;
public String getProp1() {
return prop1;
}
public void setProp1(String prop1) {
this.prop1 = prop1;
}
}
}
Thanks for your help.
Best regards
Sebastian
EDIT:
With this class I can set the value of the model directly. I will test this class in the next days and comment on this post with my result.
public class MySimpleStringProperty extends SimpleStringProperty {
public MySimpleStringProperty(Object obj, String name) {
super(obj, name);
}
public MySimpleStringProperty(Object obj, String name, String initVal) {
super(obj, name, initVal);
}
#Override
public void set(String arg0) {
super.set(arg0);
if (this.getBean() != null) {
try {
Field f = this.getBean().getClass().getDeclaredField(this.getName());
f.setAccessible(true);
f.set(this.getBean(), arg0);
} catch (NoSuchFieldException e) {
// logging here
} catch (SecurityException e) {
// logging here
} catch (IllegalArgumentException e) {
// logging here
} catch (IllegalAccessException e) {
// logging here
}
}
}
}
This constructor doesn't attach SimpleStringProperty to a bean object unfortunately. It just says to SimpleStringProperty which bean property belongs to.
E.g., if you want to have a property in your class you should do it next way:
public static class Model {
private StringProperty prop1 =
new SimpleStringProperty(this, "prop1", "default_value");
public String getProp1() {
return prop1.get();
}
public void setProp1(String value) {
prop1.set(value);
}
public StringProperty prop1Property() {
return prop1;
}
}
Note, that there is no way to bind to your original Model class as it provides no events about setting new prop1 value. If you want to have observable model, you should use fx properties from the beginning.
Just figured out that there is provided the class JavaBeanStringProperty, which just fullfill my request.
Using this code I can directly bind the value of my bean to a StringProperty (included setting and getting of my value to / from my Bean).
binding = JavaBeanStringPropertyBuilder.create().beanClass(Model.class).bean(model).name("prop1").build();
The only problem I found is that when you change the value of the model after setting the binding, there is no update e.g. in the TextField.
Related
Please note: I am using Spring Boot here, not Spring MVC.
Java 8 and Spring Boot/Jackson here. I have the following enum:
public enum OrderType {
PARTIAL("partialOrder"),
FULL("fullOrder");
private String label;
OrderType(String label) { this.label = label; }
}
I would like to expose a POST endpoint where the client can place the OrderType#label as a request parameter, and Spring will know to convert the provided label into an OrderType like so:
#PostMapping("/v1/myapp/orders")
public ResponseEntity<?> acceptOrder(#RequestParam(value = "orderType") OrderType orderType) {
// ...
}
And hence the client could make a call such as POST /v1/myapp/orders?orderType=fullOrder and on the server, the controller would receive an OrderType instance.
How can I accomplish this?
This was so easy, a cave man could even do it.
Enum:
public class MyEnum {
FIZZ("sumpin"),
BUZZ("sumpinElse");
#JsonValue
private String label;
MyEnum(String label) { this.label = label; }
public String getLabel() { return this.label; }
public static Optional<MyEnum> toMyEnum(String label) {
if (label == null) {
return Optional.empty();
}
for (MyEnum mine : MyEnum.values()) {
if (label.equals(mine.getLabel()) {
return Optional.of(mine);
}
}
throw new IllegalArgumentException("no supported");
}
}
Spring converter:
public class MyEnumConverter implements Converter<String,MyEnum> {
#Override
public MyEnum convert(String label) {
Optional<MyEnum> maybeMine = MyEnum.toMyEnum(label);
if (maybeMine.isPresent()) {
return maybeMine.get():
}
// else, you figure out what you want your app to do,
// thats not my job!
}
}
Register it:
#Configuration
public class YourAppConfig {
#Autowired
public void configureConverter(FormatterRegistry registry) {
registry.addConverter(new MyEnumConverter());
}
}
Support it from inside in a controller/resource:
#Post("/v1/foobar/doSomething")
public ResponseEntity<?> doSomething(#RequestParam(value = "mine") MyEnum mine) {
// ... whatever
}
Use the darn thing in an API call:
POST http://yourlousyapp.example.com/v1/foobar/doSomething?mine=sumpin
Wicket use of models can be cumbersome. For a stateful page to properly render an object, you need to use lots of boiler-plate code, overriding classes to properly get the visibility status, etc... A simple example:
private IModel<FooBar> fooBarModel;
public MyPage() {
Label lbl1 = new Label("field1",
new PropertyModel<>(fooBarModel, "field1")) {
#Override public boolean isVisible() {
return fooBarModel.getObject().someCondition();
} }
add(lbl1);
/// Etc... same for dozen of other fields
}
I'm often using a trick using a ListView to help. Same example:
public MyPage() {
add(new ListView<FooBar>("content",
new SingleListModel<FooBar>(fooBarModel)) {
#Override protected void populateItem(...) {
FooBar fooBar = item.getModelObject();
// Code here gets simpler:
Label lbl1 = new Label("field1", fooBar.getField1());
lbl1.setVisible(fooBar.someCondition());
item.add(lbl1);
// Etc...
}
});
}
With a simple utility class SingleListModel, that transform a IModel<T> to a ListModel<T>, having 1 or 0 elements, depending whether T is null or not:
public class SingleListModel<T>
extends LoadableDetachableModel<List<T>> {
private IModel<T> tModel;
public SingleListModel(IModel<T> tModel) {
this.tModel = tModel;
}
#Override
protected List<T> load() {
List<T> ret = new ArrayList<>(1);
T t = tModel.getObject();
if (t != null)
ret.add(tModel.getObject());
return ret;
}
}
The nice side-effect of this is that the whole "content" element in the markup is hidden if fooBarModel returns null; no special treatment needed.
But all this smells like a hack to me, as I use ListView in a somehow "unnatural" fashion.
Is there a cleaner way to get the same result? A standard wicket framework?
You should use Behavior instead to avoid such duplications.
public class MyBehavior extends Behavior {
private final MyModel model;
public MyBehavior(MyModel model) {this.model = model;}
#Override public void onConfigure(Component c) {
if (model.someCondition()) {
component.setVisible(false);
}
}
}
Usage:
MyBehavior b = new MyBehavior(modelInstance);
component1.add(b);
component2.add(b);
// dozen more
Label lbl1 = new Label("field1",
new PropertyModel<>(fooBarModel, "field1")) {
#Override public boolean isVisible() {
return fooBarModel.getObject().someCondition();
} }
add(lbl1);
with little refactoring it can be converted into
add(new FLabel("id","text")
.setVisibilityFunction(()->model.getObject().isVisible()))
);
the FLabel class:
public class FLabel extends Label implements IComponentWithVisibilityFunction<FLabel> {
private SerializableBooleanSupplier visibilityFunction;
public FLabel(String id) {
super(id);
}
public FLabel(String id, Serializable label) {
super(id, label);
}
public FLabel(String id, IModel<?> model) {
super(id, model);
}
#Override
public FLabel setVisibilityFunction(SerializableBooleanSupplier visibilityFunction) {
this.visibilityFunction = visibilityFunction;
return this;
}
#Override
protected void onConfigure() {
if (visibilityFunction != null) {
setVisible(visibilityFunction.getAsBoolean());
}
}
}
public interface IComponentWithVisibilityFunction<T> {
T setVisibilityFunction(SerializableBooleanSupplier visibilityFunction);
}
Moreover you can put supplier into constructor:
add(new FLabel("id","text", ()->model.getObject().isVisible()));
I have an object with bound members in JavaFX andI want to reset the members to initial state at a certain moment.
So my first option is to reset them one by one, which is to much code, but its works fine.
The other option is to re-instantiate i.e. myModel = new Model(), but in the case the object reference changes and I lose my binding.
Is there any alternative way to accomplish this?
EDIT
Model
public class Service extends BaseModel {
private StringProperty serviceType = new SimpleStringProperty();
private ObjectProperty<BigDecimal> buyingPrice = new SimpleObjectProperty<>(new BigDecimal(0));
private ObjectProperty<BigDecimal> sellingPrice = new SimpleObjectProperty<>(new BigDecimal(0));
public Service(){
}
public String getServiceType() {
return bundle.getString("service");
}
public StringProperty serviceTypeProperty() {
return serviceType;
}
public void setServiceType(String serviceType) {
this.serviceType.set(serviceType);
}
public BigDecimal getBuyingPrice() {
return buyingPrice.get();
}
public ObjectProperty<BigDecimal> buyingPriceProperty() {
return buyingPrice;
}
public void setBuyingPrice(BigDecimal buyingPrice) {
this.buyingPrice.set(buyingPrice);
}
public BigDecimal getSellingPrice() {
return sellingPrice.get();
}
public ObjectProperty<BigDecimal> sellingPriceProperty() {
return sellingPrice;
}
public void setSellingPrice(BigDecimal sellingPrice) {
this.sellingPrice.set(sellingPrice);
}
}
The bindings:
public class ServiceForm extends HBox implements Initializable {
private Service service = new Service();
#FXML
private TextField serviceDescriptionField;
#FXML
private TextField servicePriceField;
#FXML
private Button addButton;
#Override
public void initialize(URL url, ResourceBundle resourceBundle) {
serviceDescriptionField.textProperty().bindBidirectional(service.descriptionProperty());
servicePriceField.textProperty().bindBidirectional(service.sellingPriceProperty(), new BigDecimalStringConverter());
}
public Service getService() {
return service;
}
public void setService(Service service) {
this.service = service;
}
public Button getAddButton() {
return addButton;
}
public void setAddButton(Button addButton) {
this.addButton = addButton;
}
}
Doing this does not clear my fields and I believe it breaks my binding:
serviceForm.setService(new Service());
You can try to have the Service as a property:
private ObjectProperty<Service> service = new SimpleObjectProperty<>(new Service());
Extract the bindings into a private method which is able to unbind old bindings and create new ones:
private void createBindings(Service oldService) {
// Unbind if there is an older service
if (oldService != null) {
servicePriceField.textProperty().unbindBidirectional(oldService.sellingPriceProperty());
serviceDescriptionField.textProperty().unbindBidirectional(oldService.descriptionProperty());
}
servicePriceField.textProperty().bindBidirectional(service.get().sellingPriceProperty(), new BigDecimalStringConverter());
serviceDescriptionField.textProperty().bindBidirectional(service.descriptionProperty());
}
and then in the initialize method:
createBindings(null);
service.addListener((obs, oldval, newval) -> createBindings(oldval));
This way if you call service.set(new Service()), the bindings will be created for the new Service and will be removed for the previous one.
Now when you add new bindings, you can bundle them in the createBindings method while adding the unbinging logic to the same place. Note: this could be further generalized.
I want to use the default JAX-RS response deserializer.
Here is my POJO
#JsonIgnoreProperties(ignoreUnknown = true)
public class Email
{
private String mFrom;
private List<String> mTo;
private List<String> mCc;
private List<String> mBcc;
private String mSubject;
private String mText;
public void setFrom(String from)
{
mFrom = from;
}
#JsonProperty("from")
public String getFrom()
{
return mFrom;
}
#JsonProperty("to")
public List<String> getTo()
{
return mTo;
}
public void setTo(List<String> to)
{
mTo = to;
}
#JsonProperty("carbon_copy")
public List<String> getCc()
{
return mCc;
}
public void setCc(List<String> cc)
{
mCc = cc;
}
#JsonProperty("blind_carbon_copy")
public List<String> getBcc()
{
return mBcc;
}
public void setBcc(List<String> bcc)
{
mBcc = bcc;
}
}
This my JAX-RS code.
#GET
#Produces("application/json", "application/xml", "text/xml")
public Response getEmails() {
List<Email> emails = getEmails(); //returns list of emails
return Response.ok(emails).build();
}
output
[{"from":"example#isp.com","to":[ ],"cC":[ ],"bCc":[ ],"subject":"my subject","text":"email from admin"}]
I want to change "cC" to the "carbon_copy". I want to solve this using the JAX-RS Response. How do I get JAX-RS to use the jackson annotated property name. Do I need to override something?
My current implementation i did the following.
public class JsonDeserializer
{
private static ObjectMapper mMapper;
static
{
mMapper = new ObjectMapper();
mMapper.setSerializationInclusion(Inclusion.NON_NULL);
}
#SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> T fromInputStream(InputStream is, Class t)
{
try
{
return (T) mMapper.readValue(is, t);
}
catch (JsonParseException e)
{
e.printStackTrace();
}
catch (JsonMappingException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ClassCastException e)
{
e.printStackTrace();
}
return null;
}
}
Response.ok(JsonDeserializer.toJson(emails)).build();
Is there away to do it without creating another class to handle the deserialization process.
Mix-in can help you resolve this. You need to create an abstract class say "EmailExpanded" that has the property something like this:
#JsonProperty("carbon_copy")
public abstract List<String> getCc();
Then add that mixin:
emailExpandMapper = new ObjectMapper();
emailExpandMapper.getSerializationConfig().addMixInAnnotations(
Email.class, EmailExpanded.class);
emailExpandMapper.getSerializationConfig().setSerializationInclusion(
Inclusion.NON_NULL);
Later in the code while you send the response:
emailExpandMapper.writeValueAsString(emails)
You can read more about Mixins in the web.
I have a bean, which I attached to the form using Model and it works fine. Also I have a field in bean like Map<String, javax.mail.Address>. How can I bind this field with form by model considering that every map entry should be like Label: TextField?
Thanks in advance.
Maybe something like:
ListView<String> textAreasListView = new ListView<String>("someid", bean.map.keySet()) {
#Override
protected void populateItem(final ListItem<String> itemLang) {
itemLang.add(new Label("label", itemLang.getModelObject()));
Model<String> textModel = new Model<String>() {
#Override
public String getObject() {
return bean.map.get(itemLang.getModelObject()).toString;
}
#Override
public void setObject(String object) {
bean.map.put(itemLang.getModelObject(), new Address(object));
}
};
itemLang.add(new TextField<String>("email", textModel));
}
};
and add this to your form.
A custom converter for Address & String would be an additional improvement.
I think, you could bind it in such a way:
TextField<Address> textField = new TextField<Address>("address",
beanModel.<Address> bind("addressMap[addressKey]"), Address.class) {
#SuppressWarnings("unchecked")
#Override
public <C> IConverter<C> getConverter(Class<C> type) {
if (Address.class.isAssignableFrom(type)) {
return (IConverter<C>) new AddressConverter();
} else {
return super.getConverter(type);
}
}
};
form.add(textField);
Here "addressMap" is name of the map field, "addressKey" is the key of the address value in the map.
The listing for AddressConverter class:
public class AddressConverter implements IConverter<Address> {
public Address convertToObject(String string, Locale locale) {
try {
return new InternetAddress(string);
} catch (AddressException e) {
return null;
}
}
public String convertToString(Address address, Locale locale) {
return address.toString();
}
}
Of course, converter for Address class may be attached globally with the ConverterLocator. Just add the following method to your Application class:
#Override
protected IConverterLocator newConverterLocator() {
ConverterLocator locator = new ConverterLocator();
locator.set(Address.class, new AddressConverter());
return locator;
}
And then you don't need to override getConverter() method on the all text fields.