Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8091208

Add support for "always editable" table cells

    Details

      Description

      Would be good to have a way of creating TableViews where the content is always editable (without having to double click on a cell) in an easy way.... and... where it is easy to update other cells in that same row

      After a few experiments, we've achieved that, but through a pretty loop the loop sort of thing by extending TableRow awith one InputField (or editable control) for each column and binding/unbinding in itemProperty change event, an abstract CellFactory used by anonymous inner classes.... one teaspoon of grated ginger and a bit salt...

      being something that one would think is a quite common use case, I was wondering if there is either an easy way of doing it or if the JavaFX api can add something to make it easier

      This is what it has taken us

      -------------------------------------------------------------------------------------------------------------------------------------------------
      Abstract cell factory....
      -------------------------------------------------------------------------------------------------------------------------------------------------


      public abstract class ControlTableCellFactory<R extends TableRow<M>, M, T> implements Callback<TableColumn<M, T>, TableCell<M, T>> {
          @Override
          public TableCell<M, T> call(TableColumn<M, T> column) {
              return new ControlTableCell<R, M, T>() {
                  @Override
                  public Node getGraphic(R row) {
                      return ControlTableCellFactory.this.getGraphic(row);
                  }
              };
          }
          
          public abstract Node getGraphic(R row);

      -------------------------------------------------------------------------------------------------------------------------------------------------
      Which gets use like this....
      -------------------------------------------------------------------------------------------------------------------------------------------------

      taxColumn.setCellFactory(
                      new ControlTableCellFactory<PurchaseOrderLineTableRow, PurchaseOrderLineModel, BigDecimal>() {
                  @Override
                  public Node getGraphic(PurchaseOrderLineTableRow row) {
                      return row.getTax();
                  }
              });

      -------------------------------------------------------------------------------------------------------------------------------------------------
      Table row...
      -------------------------------------------------------------------------------------------------------------------------------------------------


      /*
       * Copyright © - 2013 Crane Technical Services Pty Ltd. All rights reserved.
       */
      package com.anahata.jobtracking.ui.jfx.purchaseorder;

      import com.anahata.jobtracking.domain.model.product.GenericProduct;
      import com.anahata.jobtracking.domain.model.product.SpecificProduct;
      import com.anahata.jobtracking.domain.model.purchaseorder.PurchaseOrderLineType;
      import com.anahata.jobtracking.ui.jfx.product.ProductDataFormatter;
      import com.anahata.util.jfx.scene.control.AutoCompleteTextField;
      import com.anahata.util.jfx.scene.control.AutoCompleteTextField.DataProvider;
      import com.anahata.util.jfx.scene.control.AutoCompleteTextField.Mode;
      import com.anahata.util.jfx.scene.control.DisplayableCellFactory;
      import com.anahata.util.jfx.scene.control.MoneyField;
      import java.math.BigDecimal;
      import javafx.application.Platform;
      import javafx.beans.binding.Bindings;
      import javafx.beans.binding.BooleanBinding;
      import javafx.beans.property.Property;
      import javafx.beans.value.ChangeListener;
      import javafx.beans.value.ObservableValue;
      import javafx.event.EventHandler;
      import javafx.scene.control.ComboBox;
      import javafx.scene.control.TableRow;
      import javafx.scene.control.TextField;
      import javafx.scene.input.KeyCode;
      import javafx.scene.input.KeyEvent;
      import javafx.util.converter.IntegerStringConverter;
      import lombok.Getter;

      /**
       * TableView row for a Purchase Order line. Contains all editable components.
       *
       * @author Pablo Rodriguez Pina <pablo@anahata-it.com.au>
       */
      public class PurchaseOrderLineTableRow extends TableRow<PurchaseOrderLineModel> {
          @Getter
          private ComboBox<PurchaseOrderLineType> type = new ComboBox<>();

          @Getter
          private TextField qty = new TextField();

          @Getter
          private AutoCompleteTextField product = new AutoCompleteTextField();

          @Getter
          private MoneyField grossPrice = new MoneyField();

          @Getter
          private ComboBox<BigDecimal> tax;

          private PurchaseOrderController purchaseOrderController;

          /**
           * Required args constructor.
           *
           * @param controller the parent controller.
           * @param productDataProvider data provider for product lookups.
           * @param taxCombo a preconfigured tax combo
           */
          public PurchaseOrderLineTableRow(PurchaseOrderController controller, DataProvider productDataProvider,
                  ComboBox<BigDecimal> taxCombo) {
              DisplayableCellFactory.setComboBoxCellFactory(type);
              purchaseOrderController = controller;
              tax = taxCombo;
              product.modeProperty().bind(Bindings.when(type.valueProperty().isEqualTo(PurchaseOrderLineType.PRODUCTS)).then(
                      AutoCompleteTextField.Mode.TYPED_ONLY).otherwise(Mode.STRING_ONLY));
              product.dataProviderProperty().bind(
                      Bindings.when(type.valueProperty().isEqualTo(PurchaseOrderLineType.PRODUCTS)).then(productDataProvider).otherwise(
                      (DataProvider)null));
              product.setFilterItems(false);
              product.setDataFormatter(new ProductDataFormatter(true));
              grossPrice.setCurrencyVisible(false);
              grossPrice.disableProperty().bind(product.valueProperty().isNull().or(product.valueProperty().isEqualTo("")));
              tax.disableProperty().bind(product.valueProperty().isNull());

              //Select the row when any of the editable components gains focus
              //todo change grossPrice.focusedProperty to controlsFocusedProperty
              BooleanBinding focused = product.textFieldFocusedProperty().or(grossPrice.controlsFocusedProperty()).or(
                      qty.focusedProperty()).or(
                      tax.focusedProperty());
              focused.addListener(new ChangeListener<Boolean>() {
                  @Override
                  public void changed(
                          ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                      if (newValue) {
                          getTableView().getSelectionModel().clearSelection();
                          getTableView().getSelectionModel().select(getItem());
                      }
                  }
              });
              //Automatic line addition
              grossPrice.getAmount().addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
                  @Override
                  public void handle(KeyEvent event) {
                      if (event.getCode() == KeyCode.TAB) {
                          if (!purchaseOrderController.isGstVisible()) {
                              if (getTableView().getItems().indexOf(getItem()) == getTableView().getItems().size() - 1) {
                                  purchaseOrderController.addLines(1);
                              }
                          }
                      }
                  }
              });

              tax.addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
                  @Override
                  public void handle(KeyEvent event) {
                      if (event.getCode() == KeyCode.TAB) {
                          if (getTableView().getItems().indexOf(getItem()) == getTableView().getItems().size() - 1) {
                              purchaseOrderController.addLines(1);
                          }
                      }
                  }
              });

              product.valueProperty().addListener(new ChangeListener() {
                  @Override
                  public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                      checkStyle();
                  }
              });

              disableProperty().addListener(new ChangeListener<Boolean>() {
                  @Override
                  public void changed(
                          ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                      checkStyle();
                  }
              });

              super.itemProperty().addListener(new ChangeListener<PurchaseOrderLineModel>() {
                  @Override
                  public void changed(ObservableValue<? extends PurchaseOrderLineModel> observable,
                          PurchaseOrderLineModel oldValue, final PurchaseOrderLineModel newValue) {
                      if (oldValue != null) {
                          type.valueProperty().unbindBidirectional((Property)oldValue.typeProperty());
                          qty.textProperty().unbindBidirectional((Property)oldValue.qtyProperty());
                          product.valueProperty().unbindBidirectional(oldValue.productProperty());
                          grossPrice.valueProperty().unbindBidirectional(oldValue.priceProperty());
                          grossPrice.amountTextProperty().removeListener(oldValue.getChangeListener());
                          tax.valueProperty().unbindBidirectional(oldValue.taxProperty());
                          disableProperty().unbind();
                      }

                      if (newValue != null) {
                          disableProperty().bind(newValue.activeProperty().not());
                          type.getSelectionModel().clearSelection();
                          type.getItems().clear();
                          type.getItems().setAll(newValue.getDelegate().getValidLineTypes());
                          type.valueProperty().bindBidirectional((Property)newValue.typeProperty());
                          type.getSelectionModel().select(newValue.typeProperty().getValue());
                          qty.textProperty().bindBidirectional((Property)newValue.qtyProperty(), new IntegerStringConverter());
                          product.valueProperty().bindBidirectional(newValue.productProperty());
                          grossPrice.valueProperty().bindBidirectional(newValue.priceProperty());
                          grossPrice.amountTextProperty().addListener(newValue.getChangeListener());
                          tax.valueProperty().bindBidirectional(newValue.taxProperty());
                          if (!newValue.isFocusRequested()) {
                              Platform.runLater(new Runnable() {
                                  @Override
                                  public void run() {
                                      product.requestFocus();
                                      newValue.setFocusRequested(true);
                                  }
                              });
                          }
                      }
                  }
              });
          }

          private void checkStyle() {
              getStyleClass().remove("noproduct");
              getStyleClass().remove("specificproduct");
              getStyleClass().remove("genericproduct");
              getStyleClass().remove("deleted");
              Object productValue = this.product.getValue();
              
              if (isDisabled()) {
                  getStyleClass().add("deleted");
              } else if (productValue == null || productValue instanceof String) {
                  getStyleClass().add("noproduct");
              } else if (productValue instanceof SpecificProduct) {
                  getStyleClass().add("specificproduct");
              } else if (productValue instanceof GenericProduct) {
                  getStyleClass().add("genericproduct");
              }
          }
      }

        Attachments

          Activity

            People

            • Assignee:
              aghaisas Ajit Ghaisas
              Reporter:
              atechnolojfx Anahata Technologies (Inactive)
            • Votes:
              1 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Imported: