All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jfoenix.controls.cells.editors.base.GenericEditableTableCell Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.jfoenix.controls.cells.editors.base;

import com.jfoenix.controls.cells.editors.TextFieldEditorBuilder;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;

/**
 * 

Generic Editable Table cell

* Provides the base for an editable table cell using a text field. Sub-classes can provide formatters for display and a * commitHelper to control when editing is committed. *

* * @author Shadi Shaheen * @version 1.0 * @since 2019-02-04 */ public class GenericEditableTableCell extends TableCell { protected EditorNodeBuilder builder; protected Region editorNode; protected Consumer commitExceptionConsumer = null; /** * constructor that creates the default {@link TextFieldEditorBuilder TextField} * editor node to edit the cell */ public GenericEditableTableCell() { this(new TextFieldEditorBuilder()); } /** * constructor that takes a custom builder to edit the cell * * @param builder */ public GenericEditableTableCell(EditorNodeBuilder builder) { this(builder, null); } public GenericEditableTableCell(EditorNodeBuilder builder, Consumer exConsumer) { this.builder = builder; this.commitExceptionConsumer = exConsumer; } /** * Any action attempting to commit an edit should call this method rather than commit the edit directly itself. This * method will perform any validation and conversion required on the value. For text values that normally means this * method just commits the edit but for numeric values, for example, it may first parse the given input.

The * only situation that needs to be treated specially is when the field is losing focus. If you user hits enter to * commit the cell with bad data we can happily cancel the commit and force them to enter a real value. If they * click away from the cell though we want to give them their old value back. * * @param losingFocus true if the reason for the call was because the field is losing focus. */ protected void commitHelper(boolean losingFocus) { if (editorNode == null) { return; } try { builder.validateValue(); commitEdit((T) builder.getValue()); builder.nullEditorNode(); editorNode = null; } catch (Exception ex) { if (commitExceptionConsumer != null) { commitExceptionConsumer.accept(ex); } if (losingFocus) { cancelEdit(); } } } /** * Provides the string representation of the value of this cell when the cell is not being edited. */ protected Object getValue() { return getItem(); } @Override public void startEdit() { if (isEditable()) { super.startEdit(); // focus cell (in case of validation error the focus will remain) getTableView().getFocusModel().focus(getTableRow().getIndex(), getTableColumn()); if (editorNode == null) { createEditorNode(); } else { // set current value if the editor is already created builder.setValue(getValue()); } builder.startEdit(); setGraphic(editorNode); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); } } @Override public void cancelEdit() { //Once the edit has been cancelled we no longer need the editor //so we mark it for cleanup here. Note though that you have to handle //this situation in the focus listener which gets fired at the end //of the editing. editorNode = null; super.cancelEdit(); builder.cancelEdit(); builder.setValue(getValue()); builder.nullEditorNode(); setContentDisplay(ContentDisplay.TEXT_ONLY); } @Override public void updateItem(T item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { if (isEditing()) { if (editorNode != null) { builder.setValue(getValue()); } setGraphic(editorNode); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); builder.updateItem(item, empty); } else { Object value = getValue(); if (value instanceof Node) { setGraphic((Node) value); setContentDisplay(ContentDisplay.GRAPHIC_ONLY); } else { setText(value == null ? null : String.valueOf(value)); setContentDisplay(ContentDisplay.TEXT_ONLY); } } } } private void createEditorNode() { EventHandler keyEventsHandler = t -> { if (t.getCode() == KeyCode.ENTER) { commitHelper(false); } else if (t.getCode() == KeyCode.ESCAPE) { cancelEdit(); } else if (t.getCode() == KeyCode.TAB) { commitHelper(false); editNext(!t.isShiftDown()); } }; ChangeListener focusChangeListener = (observable, oldValue, newValue) -> { //This focus listener fires at the end of cell editing when focus is lost //and when enter is pressed (because that causes the text field to lose focus). //The problem is that if enter is pressed then cancelEdit is called before this //listener runs and therefore the text field has been cleaned up. If the //text field is null we don't commit the edit. This has the useful side effect //of stopping the double commit. if (editorNode != null && !newValue) { commitHelper(true); } }; editorNode = builder.createNode(getValue(), keyEventsHandler, focusChangeListener); } private BiFunction stepFunction = (index, direction) -> 0; public BiFunction getStepFunction() { return stepFunction; } public void setStepFunction(BiFunction stepFunction) { this.stepFunction = stepFunction; } /** * @param forward true gets the column to the right, false the column to the left of the current column * @return */ private void editNext(boolean forward) { List> columns = new ArrayList<>(); for (TableColumn column : getTableView().getColumns()) { columns.addAll(getLeaves(column)); } //There is no other column that supports editing. int index = getIndex(); int nextIndex = columns.indexOf(getTableColumn()); if (forward) { nextIndex++; if (nextIndex > columns.size() - 1) { nextIndex = 0; index += stepFunction.apply(index, 1); } } else { nextIndex--; if (nextIndex < 0) { nextIndex = columns.size() - 1; index += stepFunction.apply(index, -1); } } if (columns.size() < 2 && index == getIndex()) { return; } TableColumn nextColumn = columns.get(nextIndex); if (nextColumn != null) { getTableView().edit(index, nextColumn); getTableView().scrollToColumn(nextColumn); } } private List> getLeaves(TableColumn rootColumn) { List> columns = new ArrayList<>(); if (rootColumn.getColumns().isEmpty()) { //We only want the leaves that are editable. if (rootColumn.isEditable()) { columns.add(rootColumn); } return columns; } else { for (TableColumn column : rootColumn.getColumns()) { columns.addAll(getLeaves(column)); } return columns; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy