Giter Site home page Giter Site logo

formsfx's Introduction

JFXCentral

Bugs Code Smells Lines of Code Maintainability Rating Quality Gate Status Reliability Rating Security Rating Technical Debt Vulnerabilities

FormsFX

Forms for business application made easy. Creating forms in Java has never been this easy!

Download Build Status

Maven

To use this framework as part of your Maven build simply add the following dependency to your pom.xml file.

<dependency>
  <groupId>com.dlsc.formsfx</groupId>
  <artifactId>formsfx-core</artifactId>
  <version>11.3.2</version>
</dependency>

What is FormsFX?

Creating forms in JavaFX is a tedious and very error-prone task. FormsFX is a framework which solves this problem. It enables the developer to create forms with ease and creates well-designed and user-friendly forms by default. FormsFX offers a fluent API that is very easy to understand and reduces the amount of coding needed. It creates all the necessary bindings for the properties and it just works.

Main Features

  • Simple and understandable Fluent API
  • Different semantic items
  • Pre-defined controls
  • Validation
  • Localisation
Form loginForm = Form.of(
        Group.of(
                Field.ofStringType(model.usernameProperty())
                        .label("Username"),
                Field.ofStringType(model.passwordProperty())
                        .label("Password")
                        .required("This field can’t be empty")
        )
).title("Login");

Semantics

FormsFX offers different semantic layers. The largest entity is the form. It contains groups and sections, which in turn act as containers for fields. Fields are the end user's primary point of interaction as they handle data input and presentation.

Defining a form

Creating a form is as simple as calling Form.of().

Form.of(
        Group.of(
                Field.ofStringType("")
                        .label("Username"),
                Field.ofStringType("")
                        .label("Password")
                        .required("This field can’t be empty")
        ),
        Group.of(…)
).title("Login");

Fields have a range of options that define their semantics and change their functionality.

Option Description
label(String) Describes the field’s content in a concise manner. This description is always visible and usually placed next to the editable control.
tooltip(String) This contextual hint further describes the field. It is usually displayed on hover or focus.
placeholder(String) This hint describes the expected input as long as the field is empty.
required(boolean)
required(String)
Determines, whether entry in this field is required for the correctness of the form.
editable(boolean) Determines, whether end users can edit the contents of this field.
id(String) Describes the field with a unique ID. This is not visible directly, but can be used for styling purposes.
styleClass(List&lt;String&gt;) Adds styling hooks to the field. This can be used on the view layer.
span(int)
span(ColSpan)
Determines, how many columns the field should span on the view layer. Can be a number between 1 and 12 or a ColSpan fraction.
render(SimpleControl) Determines the control that is used to render this field on the view layer.

The following table shows how to create different fields and how they look by default:

String Control

String Control
Field.ofStringType("CHF")
     .label("Currency")
Integer Control
Field.ofIntegerType(8401120)
     .label("Population")
Double Control
Field.ofDoubleType(41285.0)
       .label("Area")
Boolean Control
Field.ofBooleanType(false)
     .label("Independent")
ComboBox Control
Field.ofSingleSelectionType(Arrays.asList("Zürich (ZH)", "Bern (BE)", …), 1)
     .label("Capital")
RadioButton Control
Field.ofSingleSelectionType(Arrays.asList("Right", "Left"), 0)
     .label("Driving on the")
     .render(new SimpleRadioButtonControl<>())
CheckBox Control
Field.ofMultiSelectionType(Arrays.asList("Africa", "Asia", …), Collections.singletonList(2))
     .label("Continent")
     .render(new SimpleCheckBoxControl<>())
ListView Control
Field.ofMultiSelectionType(Arrays.asList("Zürich (ZH)", "Bern (BE)", …), Arrays.asList(0, 1, …))
     .label("Biggest Cities")

Rendering a form

The only point of interaction is the FormRenderer. It delegates rendering of further components to other renderers.

Pane root = new Pane();
root.getChildren().add(new FormRenderer(form));

All fields have a default control that is used for rendering. This can be changed to another compatible implementation using the render() method.

Field.ofMultiSelectionType(…)
        .render(new SimpleCheckBoxControl<>())

Model

Forms are used to create and manipulate data. In order to use this data in other parts of an application, model classes can be used. These classes contain properties, which are then bound to the persisted value of a field.

StringProperty name = new SimpleStringProperty("Hans");
Field.ofStringType(name);

The persist() and reset() methods can be used to store and revert field values, which in turn updates the binding.

Fields in FormsFX store their values in multiple steps. For free-form fields, like StringField or DoubleField, the exact user input is stored, along with a type-transformed value and a persistent value. The persistence is, by default, handled manually, but this can be overridden by setting the BindingMode to CONTINUOUS on the form level.

Localisation

All displayed values are localisable. Methods like label(), placeholder() accept keys which are then used for translation. By default, FormsFX includes a ResourceBundle-based implementation, however, this can be exchanged for a custom implementation.

private ResourceBundle rbDE = ResourceBundle.getBundle("demo.demo-locale", new Locale("de", "CH"));
private ResourceBundle rbEN = ResourceBundle.getBundle("demo.demo-locale", new Locale("en", "UK"));

private ResourceBundleService rbs = new ResourceBundleService(rbEN);

Form.of(…)
        .i18n(rbs);

Validation

All fields are validated whenever end users edit the contained data. FormsFX offers a wide range of pre-defined validators, but also includes support for custom validators using the CustomValidator.forPredicate() method.

Validator Description
CustomValidator Define a predicate that returns whether the field is valid or not.
DoubleRangeValidator Define a number range which is considered valid. This range can be limited in either one direction or in both directions.
IntegerRangeValidator Define a number range which is considered valid. This range can be limited in either one direction or in both directions.
RegexValidator Valiate text against a regular expression. This validator offers pre-defined expressions for common use cases, such as email addresses.
SelectionLengthValidator Define a length interval which is considered valid. This range can be limited in either one direction or in both directions.
StringLengthValidator Define a length interval which is considered valid. This range can be limited in either one direction or in both directions.

Advantages

  • Less error-prone
  • Less code needed
  • Easy to learn
  • Easy to understand
  • Easy to extend

Documentation

formsfx's People

Contributors

aalmiray avatar crschnick avatar dependabot[bot] avatar dlemmermann avatar github-actions[bot] avatar martinfrancois avatar radlikewhoa avatar rladstaetter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

formsfx's Issues

Dynamic forms

Is it possible to modify a Form once it has been created? For example, adding more fields depending a particular condition becomes true after the Form has been initially created.

DataField assumes edited value can be transformed to a suitable String representation

DataField<V> assumes that V can be safely transformed to a String literal that makes sense for that particular type, but that's not often the case, specially when dealing with types (domain classes) for which an overridden version of toString() is not available or returns a rather cryptic value.

This design decision imposes a constraint on edited values as it's expected of them to provide a suitable toString() implementation, while at the same time requiring an external transformer (from String to V) by means of FormsFX's ValueTransformer.

It would be much better if JavaFX's StringConverter would be used instead, as it provides both sides of the equation. Retrofitting DataField with StrinfConverter can be done in a compatible way, by deprecating ValueTransformer and redirecting is String -> T transformation to an ad-hoc StringConverter.fromString. Also, a pre-instantiated StringConverter can take care of the default String conversion.

Required field fails when I18N is enabled

Define a Field with required(true) and enabled I18N by invoking i18n(). This will cause an exception because there's no errorMessage associated with this field however the errorMessageKey property has an empty String as value but the check is made against a null value.

The offending code is found in DataField.validate()

    if (!validateRequired(newValue)) {
        if (isI18N() && requiredErrorKey.get() != null) {
            errorMessageKeys.setAll(requiredErrorKey.get());
        } else if (requiredError.get() != null) {
            errorMessages.setAll(requiredError.get());
        }

        valid.set(false);
        return false;
    }

Interestingly enough, formatting after transforming the value (inside the same validate() method) has the correct check

    try {
        transformedValue = valueTransformer.transform(newValue);
    } catch (Exception e) {
        if (isI18N() && !formatErrorKey.get().isEmpty()) {
            errorMessageKeys.setAll(formatErrorKey.get());
        } else if (!formatError.get().isEmpty()) {
            errorMessages.setAll(formatError.get());
        }

        valid.set(false);
        return false;
    }

Full stack trace follows

Exception in thread "JavaFX Application Thread" java.util.MissingResourceException: Can't find resource for bundle java.util.PropertyResourceBundle, key 
    at java.util.ResourceBundle.getObject(ResourceBundle.java:450)
    at java.util.ResourceBundle.getString(ResourceBundle.java:407)
    at com.dlsc.formsfx.model.util.ResourceBundleService.translate(ResourceBundleService.java:62)
    at com.dlsc.formsfx.model.structure.Field.lambda$null$5(Field.java:170)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Iterator.forEachRemaining(Iterator.java:116)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at com.dlsc.formsfx.model.structure.Field.lambda$new$6(Field.java:171)
    at com.sun.javafx.binding.ListExpressionHelper$SingleChange.fireValueChangedEvent(ListExpressionHelper.java:239)
    at com.sun.javafx.binding.ListExpressionHelper.fireValueChangedEvent(ListExpressionHelper.java:109)
    at javafx.beans.property.ListPropertyBase.fireValueChangedEvent(ListPropertyBase.java:200)
    at javafx.beans.property.ListPropertyBase.lambda$new$39(ListPropertyBase.java:56)
    at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
    at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
    at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
    at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
    at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
    at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
    at javafx.collections.ModifiableObservableListBase.setAll(ModifiableObservableListBase.java:90)
    at javafx.collections.ObservableListBase.setAll(ObservableListBase.java:250)
    at javafx.beans.binding.ListExpression.setAll(ListExpression.java:364)
    at com.dlsc.formsfx.model.structure.DataField.validate(DataField.java:303)
    at com.dlsc.formsfx.model.structure.DataField.lambda$new$3(DataField.java:126)
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:103)
    at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:110)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:144)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:49)
    at javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
    at javafx.beans.property.StringProperty.setValue(StringProperty.java:57)
    at com.sun.javafx.binding.BidirectionalBinding$TypedGenericBidirectionalBinding.changed(BidirectionalBinding.java:599)
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.scene.control.TextInputControl$TextProperty.fireValueChangedEvent(TextInputControl.java:1389)
    at javafx.scene.control.TextInputControl$TextProperty.markInvalid(TextInputControl.java:1393)
    at javafx.scene.control.TextInputControl$TextProperty.controlContentHasChanged(TextInputControl.java:1332)
    at javafx.scene.control.TextInputControl$TextProperty.access$1600(TextInputControl.java:1300)
    at javafx.scene.control.TextInputControl.lambda$new$162(TextInputControl.java:139)
    at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:137)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.scene.control.TextField$TextFieldContent.delete(TextField.java:96)
    at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:1200)
    at javafx.scene.control.TextInputControl.updateContent(TextInputControl.java:556)
    at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:548)
    at javafx.scene.control.TextInputControl.replaceText(TextInputControl.java:510)
    at javafx.scene.control.TextInputControl.replaceSelection(TextInputControl.java:1084)
    at javafx.scene.control.TextInputControl.deletePreviousChar(TextInputControl.java:889)
    at com.sun.javafx.scene.control.skin.TextFieldSkin.deleteChar(TextFieldSkin.java:589)
    at com.sun.javafx.scene.control.behavior.TextFieldBehavior.deleteChar(TextFieldBehavior.java:198)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.deletePreviousChar(TextInputControlBehavior.java:311)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:143)
    at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
    at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
    at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
    at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
    at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:217)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:149)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$352(GlassViewEventHandler.java:248)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:247)
    at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
    at com.sun.glass.ui.View.notifyKey(View.java:966)

Validators do not handle null input

For example in IntegerRangeValidator:

public class IntegerRangeValidator extends CustomValidator<Integer> {

    private IntegerRangeValidator(int min, int max, String errorMessage) {
        super(input -> input >= min && input <= max, errorMessage);
    }
 ....

Could be:

public class IntegerRangeValidator extends CustomValidator<Integer> {

    private IntegerRangeValidator(int min, int max, String errorMessage) {
        super(input -> null != input && input >= min && input <= max, errorMessage);
    }
 ....

Some background on my usage:
I had set the IntegerRangeValidator into a SingleSelectionField (Selection bound to an IntegerProperty).
Due to some design constraint, I needed to change the SingleSelectionField content using SingleSelectionField.items(List).. This part nullifies the bound IntegerProperty until I use SingleSelectionField.select(0).
This causes an unhandled NullPointerException.

Kindly correct me if I overlooked a better approach.

Dynamic Forms,Access to values

So this may be similar to one of the open or closed issues,but please hear my case ,

How does one handle dynamic form inputs with this library, from my end I failed to capture the new values from the text input fields. My forms are being built by a JSON data sort that creates forms on the fly, but I have failed to capture the input from the user, according to the API, I seem or it seems like I can't access that from the API, I can say I have attempted to exhaust all possible options from trying to leverage KeyEvent.KEY_PRESSED (terrible idea), to even to use changedProperty() , even tried to use the native reactive API components of the JavaFX but still only get boolean values from this class javafx.beans.property.SimpleBooleanProperty.

I have studied the demo code but it fails to give practical hints to what I am looking for , may be I am miss guided please help me .

Support additional descriptions on Field

Similar to dlsc-software-consulting-gmbh/PreferencesFX#23 sometimes you'd like additional text to be displayed under a Field's name or it's renderer. This text may be used as a hint to the user to let them know what data may be entered as the Field's value.

It may be worth using Node as a type instead of just String as this would allow for additional elements (such as icons) to be embedded in the description.

I propose to add two more properties to Field: labelDescription(Node) and valueDescription(Node)/fieldDescription(Node).

com.dlsc.formsfx.model.structure.Element visibility

Hi 👋,

Could you, please, guide me how to change visibility for com.dlsc.formsfx.model.structure.Element ?

For example, I need to change visibility of one of my components. In Swing and JavaFX I just can setVisible(false) or setVisible(true). How can I do this with FormsFX?

Thank you for help!

Best regards,
Alex

Composite fields

The following screenshot depicts a TextField and a CheckBox as part of the same "field" row (they share the same label)

formsfx-composite-field

There's also additional content (the Test Button) which may or may no be part of a custom renderer.

RegexValidator.forURL is overly restrictive

Regex is here:

return new RegexValidator("(https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]\\.[^\\s]{2,})", errorMessage);

Basically this marks urls like https://localhost:80/ as invalid.

Setting width of Labels

Hi there,

Having difficulty setting the width of the Labels.. would be good to have room for a little more text.
Have been able to adjust the individual widths of the label and the control with CSS but this is creating an overlap as shown in image below. Any help would be much appreciated!

Many thanks

.changeLabel .label {

-fx-font-family: "DS-Digital Bold";
-fx-font-size: 130%;
-fx-min-width:150px;

}

.changeLabel .combo-box-base{
-fx-max-width:150px;
}

image

Make Field Instance Variable Protected In SimpleControl

I'm attempting to create a new SimpleControl subclass called SimpleHtmlEditorControl for my project and I cannot access the private field instance variable in SimpleControl in my subclass because it is private. This is the code that I wrote:

public class SimpleHtmlEditorControl extends SimpleControl<StringField> {

    private StackPane stackPane;

    private Label fieldLabel;
    private Label readOnlyLabel;
    private HTMLEditor htmlEditor;

    @Override
    public void initializeParts() {
        super.initializeParts();

        stackPane = new StackPane();

        fieldLabel = new Label();
        readOnlyLabel = new Label();

        htmlEditor = new HTMLEditor();
    }

    @Override
    public void layoutParts() {
        super.layoutParts();

        readOnlyLabel.getStyleClass().add("read-only-label");

        readOnlyLabel.setPrefHeight(26);

        stackPane.getChildren().addAll(htmlEditor, readOnlyLabel);

        stackPane.setAlignment(Pos.CENTER_LEFT);

        // Cannot access private instance variable 'field' here.
        int columns = 12;

        if (columns < 3) {
            add(fieldLabel, 0, 0, columns, 1);
            add(stackPane, 0, 1, columns, 1);
        } else {
            add(fieldLabel, 0, 0, 2, 1);
            add(stackPane, 2, 0, columns - 2, 1);
        }
    }

    @Override
    public void setupBindings() {
        super.setupBindings();
    }

    @Override
    public void setupValueChangedListeners() {
        super.setupValueChangedListeners();
    }
}

If I hardcode the column count, as you can see below my inline code comment above, the HTMLEditor is displayed fine. Otherwise, if I try to to call getSpan() on the field to get the field's actual column span I get a compile time error. Can you please make that field protected so subclasses can use it?

Using JavaBeanXXProperty will not update the model

Hello. I used the JavaBeanDoubleProperty, JavaBeanStringProperty, etc. in my model, but the form is not updating the fields if changed in the GUI.

        p = new ObservableGameMainPaneProperties.GameMainPaneProperties();
        np = new ObservableGameMainPaneProperties(p);
        this.form = Form.of(Group.of(//
                Field.ofStringType(np.kernelName).label("Name").required("Not empty"), //
                Field.ofIntegerType(np.seed).label("Seed").required("Not empty"), //
                Field.ofIntegerType(np.width).label("Width").required("Not empty"), //
                Field.ofIntegerType(np.height).label("Height").required("Not empty"), //
                Field.ofDoubleType(np.z).label("Z").required("Not empty"), //
                Field.ofIntegerType(np.dim).label("Dimension").required("Not empty") //
        ));

I created a demo project https://github.com/devent/formsfx-javabeanproperty-test
You have to change the field "Dimension" to "4". I expect that if I do that then the field ObservableGameMainPaneProperties#GameMainPaneProperties#dim is also set to 4.

Problem setting list of values of SingleSelectionField after it has been created

The attached code showcases a problem I recently encountered. It appears it's not possible to defer setting the list of values of a SingleSelectionField and have it match automatically if there's already a selection. In my use case, the selected value is known ahead of time (loaded from a DataSource) and the list of possible matches is calculated after the Form has been created.

In the attached sample code this would be the list3/value3 combination.

import com.dlsc.formsfx.model.structure.Field;
import com.dlsc.formsfx.model.structure.Form;
import com.dlsc.formsfx.model.structure.Group;
import com.dlsc.formsfx.model.structure.SingleSelectionField;
import com.dlsc.formsfx.view.renderer.FormRenderer;
import javafx.application.Application;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Sample extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        ObservableList<String> list1 = FXCollections.observableArrayList();
        ObservableList<String> list2 = FXCollections.observableArrayList();
        ObservableList<String> list3 = FXCollections.observableArrayList();

        ObjectProperty<String> value1 = new SimpleObjectProperty<>("2");
        ObjectProperty<String> value2 = new SimpleObjectProperty<>("2");
        ObjectProperty<String> value3 = new SimpleObjectProperty<>("2");
        ObjectProperty<String> value4 = new SimpleObjectProperty<>("2");

        ListProperty<String> prop3 = new SimpleListProperty<>(list3);
        ListProperty<String> prop4 = new SimpleListProperty<>(list3);

        SingleSelectionField field1 = Field.ofSingleSelectionType(list1);
        SingleSelectionField field2 = Field.ofSingleSelectionType(list2, 1);
        SingleSelectionField field3 = Field.ofSingleSelectionType(prop3, value3);
        SingleSelectionField field4 = Field.ofSingleSelectionType(prop4, value4);

        field1.selectionProperty().bindBidirectional(value1);
        field2.selectionProperty().bindBidirectional(value2);
        field3.selectionProperty().bindBidirectional(value3);
        field4.selectionProperty().bindBidirectional(value4);

        value1.addListener((v, o, n) -> System.out.println("VALUE1 " + n));
        value2.addListener((v, o, n) -> System.out.println("VALUE2 " + n));
        value3.addListener((v, o, n) -> System.out.println("VALUE3 " + n));
        value4.addListener((v, o, n) -> System.out.println("VALUE4 " + n));

        Form form = Form.of(
            Group.of(
                field1.label("List 1"),
                field2.label("List 2"),
                field3.label("List 3"),
                field4.label("List 4")
            )
        );

        Button button = new Button("Update");
        button.setOnAction(e -> {
            list1.clear();
            list2.clear();
            list3.clear();

            list1.addAll("1", "2", "3");
            list2.addAll("1", "2", "3");
            list3.addAll("1", "2", "3");
            prop4.setValue(FXCollections.observableArrayList("1", "2", "3"));
        });

        stage.setScene(new Scene(new VBox(new FormRenderer(form), button)));
        stage.sizeToScene();
        stage.show();
    }
}

Also, the list of items for list1 and list2 are never updated. It appears the problem is inside the factory method as it creates a copy of the input list (assumed to be a plain List<>) by wrapping it with a FXCollections.observableArrayList() instead of inspecting if the runtime type is compatible with ObservableList and use it as is.

BigDecimal

Hello,
Thank you for this product.
Not sure if this is an issue or question.
How to map BigDecimal values, which can be null?
The DoubleDataField cannot be null.

I'm thinking using SimpleObjectProperty
but not sure how.

Any help is appreciated.

BR,
Hugo

Vertical alignment of certain labels is off

Controls render their label at the center of the cell by default, which makes it look ugly when single value fields are mixed with multiple value fields.

forms-label-alignment

Perhaps it would be a good idea to set the default position to TOP, even allow customizing label placement.

Setting spacing/padding between fields

It is possible to use FormRenderer to set the spacing between Groups/Sections but I do not see a way to set the spacing between fields. I tried using GroupRenderer however its constructor is protected and there is I see to access FormRenderer specific group so it can access a specific field (I tried using getChildren() but made more mess).

Is there a different option I should have used?

Note: I just hope this project isn't dead as it really helps solve some issues and saves time.

How to access form element values?

Hi,

Just a question - in the demo project, what is the way to access the current values in the form elements, for example the text the user types in to the Country Name field? It seemed to me like when you call .persist() it should save the current form state, but apparently I'm wrong? Any guidance would be appreciated. Any chance of adding a quick demo for this to the readme?

I wanted to add the setter in the model for Country.name then do this:

        save.setOnAction(event -> model.getFormInstance().persist());
        save.setOnAction(event -> System.out.println(model.getCountry().getName()));

Filepicker

It would be nice if it had a file picker

Embed additional content inside a Group/Section

The following screenshot shows text, a link, and a button as part of the first Group/Section (Intellij preferences).

formsfx-extra-content

The current API only allows elements of type Field to be embedded on a Group/Section. AFAICT building a screen like this will require two separate Form instances.

JDK 11 compatible

I tried maven build for this application using jdk 11 but its not working

Date Field

Hello,

I'm trying FormsFx ! It rocks !
I don't find a way to manage Date Field.
Is there a way to manage Date without develop a new Control ?

Required field with no message shows empty tooltip

Setting a field with required(true) and no error message displays an empty tooltip when the field has no value. See attached screenshot

formsfx-tooltip

Here's the code that can reproduce the problem

import com.dlsc.formsfx.model.structure.Field;
import com.dlsc.formsfx.model.structure.Form;
import com.dlsc.formsfx.model.structure.Group;
import com.dlsc.formsfx.view.renderer.FormRenderer;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Sample extends Application {
   @Override
   public void start(Stage stage) throws Exception {
      StringProperty p1 = new SimpleStringProperty("p1");
      StringProperty p2 = new SimpleStringProperty("p2");
      StringProperty p3 = new SimpleStringProperty("p3");
      StringProperty p4 = new SimpleStringProperty("p4");

      Form form = Form.of(
            Group.of(
                  Field.ofStringType(p1)
                        .label("P1")
                        .required(true),
                  Field.ofStringType(p2)
                        .label("P2")
                        .required(true),
                  Field.ofStringType(p3)
                        .label("P3")
                        .required(true),
                  Field.ofStringType(p4)
                        .label("P4")
                        .required(true)
            )
      );
      stage.setScene(new Scene(new FormRenderer(form)));
      stage.sizeToScene();
      stage.show();
   }
}

Configurable spacing/padding in multiple column layout

When a multiple column layout is used (by setting span() on Fields) the labels are placed too close to the previous value. This behavior is accentuated when short labels are used. The following screenshots show this behavior with a 2 and a 4 column layout.

formfx-spacing

formsfx-spacing2

The code used for these examples is

import com.dlsc.formsfx.model.structure.Field;
import com.dlsc.formsfx.model.structure.Form;
import com.dlsc.formsfx.model.structure.Group;
import com.dlsc.formsfx.view.renderer.FormRenderer;
import com.dlsc.formsfx.view.util.ColSpan;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Sample extends Application {
   @Override
   public void start(Stage stage) throws Exception {
      StringProperty p1 = new SimpleStringProperty("p1");
      StringProperty p2 = new SimpleStringProperty("p2");
      StringProperty p3 = new SimpleStringProperty("p3");
      StringProperty p4 = new SimpleStringProperty("p4");

      Form form = Form.of(
            Group.of(
                  Field.ofStringType(p1)
                        .label("P1")
                        .required(true).span(ColSpan.QUARTER),
                  Field.ofStringType(p2)
                        .label("P2")
                        .required(true).span(ColSpan.QUARTER),
                  Field.ofStringType(p3)
                        .label("P3")
                        .required(true).span(ColSpan.QUARTER),
                  Field.ofStringType(p4)
                        .label("P4")
                        .required(true).span(ColSpan.QUARTER)
            )
      );
      stage.setScene(new Scene(new FormRenderer(form)));
      stage.sizeToScene();
      stage.show();
   }
}

It would be great to have an option to specify padding/spacing without having to override the default renderers nor providing a custom renderer.

Setting a custom StringConverter on an IntegerField can break the Spinner in SimpleIntegerControl

One can set a custom StringConverter on an IntegerField via IntegerField.format(..)
The default control for IntegerField, SimpleIntegerControl is a wrapper around an Integer based spinner. Setting a custom StringConverter on the field seems to break the spinner up-down feature, as the spinner is not aware of the StringConverter on the field. Editing the field by typing into the text-field part of the spinner works as expected.

Workaround:

  • Override SimpleIntegerControl and apply the custom converter in the "initializeParts" method.
@Override
public void initializeParts() {
    super.initializeParts();
    editableSpinner.getValueFactory().setConverter(myCustomConverter);
}

Open SimpleControl impls for extension

I have the need to change the renderer of a StringField. While I can create a custom implementation of SimpleControlsimilar to SimpleTextControlI'd have to duplicate code as the SimpleTextControl defines most of its state & behavior using the private visibility modifier.

It would be better to migrate to protected, applying the factory method pattern where it makes sense, allowing subclasses to override specific behavior.

KeyEvent on Fields

This is not an actually issue just a question / suggestion.
I did not found a way to add a keyListener to field
Field.ofStringType(mode.getModelName())
.label("name me")
.placeholder("name")

now is there any way to add KeyEvent Listener to that field ?

Single Selection Type Fields Not Persisting

I just started using FormsFX and it's been great so far. I was able to create a form with string, integer, and boolean type fields and them update a model via properties with no issues. I run into a problem though, when I try to add a single selection type field. The following are the snippets of code showing what I did (I basically followed what's in the demo application):

    private final ListProperty<Integer> allServings =
            new SimpleListProperty<>(FXCollections.observableArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    private final ObjectProperty<Integer> serving = new SimpleObjectProperty<>(1);

   /**
     * References the <code>Form</code> that is bound to this <code>Recipe</code>.
     *
     * @since 1.0.0
     */
    private Form formInstance;

    /**
     * Retrieves or creates an instance of a FormFX form using the given <code>Recipe</code>.
     *
     * @return An instance of a FormFX form using the given <code>Recipe</code>.
     *
     * @since 1.0.0
     */
    public Form getOrCreateFormInstance() {
        if (Objects.isNull(this.formInstance)) {
            createForm();
        }

        return this.formInstance;
    }

    /**
     * Helper method to initialize the FormFX form field of this <code>Recipe</code>.
     *
     * @since 1.0.0
     */
    private void createForm() {
        this.formInstance = Form.of(Group.of(
                Field.ofStringType(titleProperty())
                     .label("Title")
                     .placeholder("e.g. Chicken Wings")
                     .required("A title is required for all recipes."),
                Field.ofStringType(descriptionProperty())
                     .label("Description")
                     .placeholder("e.g. Delicious Honey BBQ wings")
                     .required("A description is required for all recipes."),
                Field.ofIntegerType(caloriesProperty())
                     .label("Calories")
                     .placeholder("e.g. 500")
                     .span(ColSpan.HALF)
                     .required("The number of calories is required for all recipes."),
                Field.ofIntegerType(cookTimeProperty())
                     .label("Cooking Time")
                     .placeholder("e.g. 60")
                     .span(ColSpan.HALF)
                     .required("The total cooking time (in minutes) is required for all recipes."),
                Field.ofSingleSelectionType(allServingsProperty(), servingProperty())
                     .label("Serving Size")
                     .placeholder("Serving Size")
                     .required("The serving size is required for all recipes."),
                Field.ofStringType(directionsProperty())
                     .label("Cooking Directions")
                     .placeholder("e.g. Bake at 375 degrees in the over for an hour and a half.")
                     .required("Cooking directions are required for all recipes."),
                Field.ofBooleanType(publishedProperty())
                     .label("Publish Recipe")))
                                .title("Recipe Form");
    }

    public Integer getServing() {
        return serving.get();
    }

    public void setServing(Integer serving) {
        this.serving.set(serving);
    }

    public ObjectProperty<Integer> servingProperty() {
        return serving;
    }

    public ObservableList<Integer> getAllServings() {
        return allServings.get();
    }

    public void setAllServings(ObservableList<Integer> allServings) {
        this.allServings.set(allServings);
    }

    public ListProperty<Integer> allServingsProperty() {
        return allServings;
    }

Here is the method that I use to save the form fields to the database:

    @FXML
    public void save(ActionEvent event) {
        this.recipe.getOrCreateFormInstance().persist();
        if (!this.recipe.getOrCreateFormInstance().isValid()) {
            return;
        }

        if (this.recipe.getId() == 0) {
            saveRecipe(this.recipe);
        }
        else {
            updateRecipe(this.recipe);
        }
    }

All other form fields save except the serving field. Am I doing anything incorrectly based on the code I've shared? Please let me know if you need anymore context.

Allow a Validator or CustomValidator to dynamically generate error messages.

Currently one can create custom Validators using CustomValidator, where one specifies a predicate and an error message in case the predicate fails. In some cases it would be useful to generate a custom message based on the failed input. Basically indicating more specifically what is wrong with the input-string.

E.g. I have a field for inputting an ipv4 network-prefix, if the first octects are ok, but the last octet is not compatible with the network mask, I want to indicate what the closest valid numbers are for the last octet, instead of just reporting that the input value is not compatible with the provided mask.

Ideas for how this could be solved:

  • Open up the ValidationResult constructor or add a static factory-method taking the error message as a parameter. This would allow one to create Validators by just implementing Validator (given that there also would be a public way to generate an OK result.
  • Modify CustomValidator somehow to allow modifying the error message before creating the result. E.g. by providing an optional error-message generator function taking the input value and returning a message.

Section.title should return Section

This method returns Group, disallowing proper method chaining such as

Section.of(...).title("Foo").collapsible(false)

The same applies to Section.collapse() which currently returns void.

Why SingleSelectionField not "only persists values when explicitly requested" like other fields?

My version:

    <dependency>
      <groupId>com.dlsc.formsfx</groupId>
      <artifactId>formsfx-core</artifactId>
      <version>1.3.1</version>
    </dependency>

For example:

import com.dlsc.formsfx.model.structure.Field;
import com.dlsc.formsfx.model.structure.Form;
import com.dlsc.formsfx.model.structure.Group;
import com.dlsc.formsfx.view.renderer.FormRenderer;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class FormsfxApp extends Application {

    public static void main(String[] args) {
        launch(args);
    }


    @Override
    public void start(Stage primaryStage) throws Exception {

        StringProperty name = new SimpleStringProperty("my name");
        name.addListener((observable, oldValue, newValue) -> System.out.println("new name:" + newValue));
        IntegerProperty age = new SimpleIntegerProperty(10);
        age.addListener((observable, oldValue, newValue) -> System.out.println("new age:" + newValue));

        ObjectProperty<String> selected = new SimpleObjectProperty<>("aaa");
        selected.addListener((observable, oldValue, newValue) -> System.out.println("select:" + newValue));
        ListProperty<String> items = new SimpleListProperty<>(
                FXCollections.observableArrayList("aaa", "bbb", "ccc")
        );

        Form form = Form.of(
                Group.of(
                        Field.ofStringType(name),
                        Field.ofIntegerType(age),
                        Field.ofSingleSelectionType(items, selected)
                )
        ).title("test persist");

        BorderPane borderPane = new BorderPane();
        borderPane.setCenter(new FormRenderer(form));
        primaryStage.setScene(new Scene(borderPane, 1000, 600));
        primaryStage.show();
    }
}

screen shot

Other Fields like StringField and IntegerField change property only when form.persis() is called explicitly. But selected property in SingleSelectionField is eagerly modified in every time I select other item in combo box.

I find there is a persistentSelection field in SingleSelectionField:

protected final ObjectProperty<V> persistentSelection = new SimpleObjectProperty<>();

But why it is not exposed for user?

PasswordFields

The framework you created is super awesome. I'm using it right now. But I have this question - How do I add a password field to a form, say, a login form

Switching to light/dark modes

Modern and popular UI's include light and dark modes. In a survey of competitive software, this seems to be a MUST have.

We've implemented light and dark modes in the rest of our software, but our forms lack this ability. I have used CSS to make changes. But these must be compiled with the library. This would not allow an end user to swap back and forth between modes.

Any suggestions on where to begin to implement this change would be appreciated. I upload my versions of this project to my github account. @runnermann

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.