Ok I did it. It works fine. Thanks for help. Here is my code. Now I only need to call my command button in a differend form to disable it and create a info there. Anyone could look about it ? In my code I got reference errors.
[ExtensionOf(formdatasourcestr(ProdTableListPage, ProdTable))]
final class ProdParmReportFinishedActiveWG_Extension
{
public int active()
{
int ret;
next Active();
{
ProdTable tableBuffer = this.cursor();
ProdTable prodtable;
if(tableBuffer.ProdId == tableBuffer.CollectRefProdId
&& tableBuffer.ProdStatus != ProdStatus::ReportedFinished)
{
select firstonly RecId,ProdId from ProdTable where
ProdTable.CollectRefProdId == tableBuffer.ProdId
&& ProdTable.Prodstatus != ProdStatus::ReportedFinished
&& tableBuffer.RecId != prodtable.RecId;
{
Global::info(strFmt("%1 , %2",
prodtable.prodid, prodtable.recid));
// FormButtonControl mybutton = this.FormRun().design().controlname(formControlStr(ProdParmReportFinished, Ok)) as FormButtonControl;
// mybutton.enabled(false);
}
}
else
{
Global::info(strFmt("%1 , %2, %3, %4",
tableBuffer.prodid, tableBuffer.CollectRefProdId, tableBuffer.InventRefType, tableBuffer.ProdStatus));
}
}
return ret;
}
}
"I want to use this code everytime user changes his actual row but instead it runs just once and apply to all my rows."
Use the selectionChanged() method instead of active().
In fact most use cases where you think you should use active(), you're probably looking for selectionChanged() (or the OnSelectionChanged event for handlers) instead.
I'm currently using TAB to navigate to next cell. selectNext() or selectRightCell() works fine when I'm using SelectionMode.SINGLE.
However, when using SelectionMode.MULTIPLE, its selecting multiple cells as I TAB.
I'm using a TableView. I need SelectionMode.MULTIPLE for the copy & paste function.
Is there a way to make it work in SelectionMode.MULTIPLE?
fixedTable.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
switch (event.getCode()){
case TAB:
if (event.isShiftDown()) {
fixedTable.getSelectionModel().selectPrevious();
} else {
fixedTable.getSelectionModel().selectNext();
}
event.consume();
break;
case ENTER:
return;
case C:
if(event.isControlDown()){
copySelectionToClipboard(fixedTable) ;
}
event.consume();
break;
case V:
if(event.isControlDown()){
pasteFromClipboard(fixedTable);
}
event.consume();
break;
default:
if (fixedTable.getEditingCell() == null) {
if (event.getCode().isLetterKey() || event.getCode().isDigitKey()) {
TablePosition focusedCellPosition = fixedTable.getFocusModel().getFocusedCell();
fixedTable.edit(focusedCellPosition.getRow(), focusedCellPosition.getTableColumn());
}
}
break;
}
}
});
You will need to handle the selection on your own. The reason is because the methods selectPrevious() and selectNext() tries to select the previous ( or the next ) without removing the current selected row ( when you set the selection mode to be SelectionMode.MULTIPLE) , also you can't use them and just remove the previous selecting by just calling clearSelection() because this will set the selected index to -1 and then the methods selectPrevious() and selectNext() will select the last or the first row only.
Here is how you could implement the selection on your own :
// the rest of your switch statement
...
case TAB:
// Find the current selected row
int currentSelection = table.getSelectionModel().getSelectedIndex();
// remove the previous selection
table.getSelectionModel().clearSelection();
if (event.isShiftDown()) {
currentSelection--;
} else {
currentSelection++;
}
// find the size of our table
int size = table.getItems().size() - 1;
// we was on the first element and we try to go back
if(currentSelection < 0){
// either do nothing or select the last entry
table.getSelectionModel().select(size);
}else if(currentSelection > size) {
// we are at the last index, do nothing or go to 0
table.getSelectionModel().select(0);
}else {
// we are between (0,size)
table.getSelectionModel().select(currentSelection);
}
event.consume();
break;
...
I'm trying to create an API, which can dynamically populate tableview with different Nested UI components in each cell of tableview. I'm able to bind the data set to Model object with the table.
The problem is when I try to dynamically add and try to enable the edit, the object reference is seems to messed up.
FYI:
As you see I've last column with 4 buttons, which is Add, Edit, Delete, Reset features. Once click on Add - it clones the current row, click on edit - it enable the ComboBox of Coulmn Category, click on delete - it deletes current row.
What I face is that upon adding multiple entries, I do get the row added dynamically, but then click on first row edit button - then multiple ComboBox is enabled, which is not intended use. The use case is current row's ComboBox must be only be enabled.
Implementation: I've written custom API which extends TableView<S>.
Following snippet may help:
//column category
final ClumpElement< ConstraintsDataModel, String > categoryElement =
new ClumpElement<>( ClumpType.COMBOBOX, true, getCategoryData() );
categoryElement.setClumpTableCellValue( data -> data.categoryProperty() );
categoryElement.setClumpTableNodeAction( ( control, data ) -> {
final ComboBox< String > comboBox = (ComboBox< String >)control;
comboBox.disableProperty().bind( data.disableProperty() );
} );
clumpTableView.addNewColumn( "Category", categoryElement );
// column Action
final ClumpElement< ConstraintsDataModel, String > buttonsElement =
new ClumpElement<>( ClumpType.GROUP_BUTTONS, 4, "+", "✎", "X", "↻" );
buttonsElement.setClumpTableNodeAction( ( control, data ) -> {
final Button button = (Button)control;
switch( button.getText() ) {
case "+":
final ConstraintsDataModel ref =
clumpTableView.getItems().get( clumpTableView.getItems().size() - 1 );
if( ConstraintsDataModel.isValidModel( ref ) )
clumpTableView.getItems().add( new ConstraintsDataModel( data ) );
else
System.out.println( "ERROR: Finish previous constraints" );
break;
case "✎":
data.setDisableValue( false );
button.setText( "✔" );
break;
case "✔":
data.setDisableValue( true );
button.setText( "✎" );
break;
default:
//NOTHING
break;
}
} );
clumpTableView.addNewColumn( "Action", buttonsElement );
clumpTableView.setItems( getData() );
This is my CustomTableView class:
public < T > void addNewColumn( final String columnName, final ClumpElement< S, T > element ) {
final TableColumn< S, T > column = new TableColumn<>( columnName );
getColumns().add( column );
if( element.getClumpTableCellValue() != null ) {
column.setCellValueFactory( param -> element.getClumpTableCellValue()
.act( param.getValue() ) );
}
clumpCellCall( columnName, element, column );
}
private < T > void clumpCellCall( final String colName, final ClumpElement< S, T > element,
final TableColumn< S, T > column ) {
switch( element.getUiNode() ) {
case COMBOBOX:
if( element.getItems() != null && !element.getItems().isEmpty() ) {
column.setCellFactory( param -> {
final ClumpComboBoxTableCell< S, T > clumpComboBoxTableCell =
new ClumpComboBoxTableCell<>( element.isDisable(), element.getItems() );
clumpComboBoxTableCell.prefWidthProperty().bind( column.widthProperty() );
clumpComboBoxTableCell.selectionListener( element );
return clumpComboBoxTableCell;
} );
}
break;
case GROUP_BUTTONS:
column.setCellFactory( param -> {
final ClumpButtonsTableCell< S, T > clumpButtonsTableCell =
new ClumpButtonsTableCell<>( element.getNoOfElements() );
clumpButtonsTableCell.prefWidthProperty().bind( column.widthProperty() );
IntStream.range( 0, element.getNoOfElements() ).forEach( item -> {
final Button button = clumpButtonsTableCell.getButtons().get( item );
button.setText( element.getNames().get( item ) );
button.setOnAction( event -> {
if( element.getClumpTableNodeAction() != null
&& clumpButtonsTableCell.getIndex() < getItems().size() ) {
element.getClumpTableNodeAction()
.act( button, getItems().get( clumpButtonsTableCell.getIndex() ) );
}
} );
} );
return clumpButtonsTableCell;
} );
break;
default:
column.setCellFactory( params -> {
final TextFieldTableCell< S, T > textFieldTableCell = new TextFieldTableCell<>();
textFieldTableCell.setConverter( new StringConverter< T >() {
#Override
public String toString( final T object ) {
return (String)object;
}
#Override
public T fromString( final String string ) {
return (T)string;
}
} );
return textFieldTableCell;
} );
break;
}
}
In my custom API, which shall invoke a custom TableCell<S,T> which has ComboBox<T> pretty standard implementation as per docs. Here its inside a selection listener, as I found that when the cell renders, only this selection listener is called.
public abstract class AbstractClumpTableCell< S, T > extends TableCell< S, T > {
public AbstractClumpTableCell() {
setContentDisplay( ContentDisplay.GRAPHIC_ONLY );
setAlignment(Pos.CENTER);
}
public abstract void renewItem( T item );
#Override
protected void updateItem( T item, boolean empty ) {
super.updateItem( item, empty );
if( empty ) {
setGraphic( null );
} else {
renewItem( item );
}
}
}
public class ClumpComboBoxTableCell< S, T > extends AbstractClumpTableCell< S, T > {
private final ComboBox< T > comboBox;
#SuppressWarnings( "unchecked" )
public ClumpComboBoxTableCell( final boolean isDisable, final ObservableList< T > item ) {
super();
this.comboBox = new ComboBox<>( item );
this.comboBox.setDisable( isDisable );
this.comboBox.valueProperty().addListener( ( obs, oVal, nVal ) -> {
ObservableValue< T > property = getTableColumn().getCellObservableValue( getIndex() );
if( property instanceof WritableValue ) {
((WritableValue< T >)property).setValue( nVal );
}
} );
}
#Override
public void renewItem( T item ) {
comboBox.setValue( item );
setGraphic( comboBox );
}
public ComboBox< T > getComboBox() {
return comboBox;
}
protected void selectionListener( final ClumpElement< S, T > element ) {
this.comboBox.getSelectionModel().selectedItemProperty().addListener( ( obs, oVal, nVal ) -> {
if( element.getClumpTableNodeAction() != null
&& getIndex() < getTableView().getItems().size() ) {
element.getClumpTableNodeAction().act( this.comboBox,
getTableView().getItems().get( getIndex() ) );
}
} );
}
}
And my Data Model has a SimpleStringProperty that is binded to the column accordingly.
So, How can I bind the Nested UI elements correctly rowise within the TableView<S>? Is my approach right or is there alternatives?
I will make an attempt to answer, but as I said the code is hard for me to follow (especially as it is partial, so some methods I can only assume the purpose of).
The issue, as stated in the comments, is node virtualization in TableView. You can't go around it, and you really don't want to - it is a means to vastly improve performance, as you don't need hundreds or thousands of UI nodes (which are "heavy" and degrade performance), but only enough to fill the displayed portion of the table, thus supporting a much larger dataset.
The problem, as far as I can see, as that you have some property of the row (is it currently editable or not) which you need to be reflected in certain columns. More specifically, you want the combo box's disable property to always reflect the disable property of the row it pertains to, so in updateItem you will have to do something like this:
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(T, empty);
if (empty) {
setGraphic(null);
} else {
renewItem(item);
// since the disable property if given by the row value, not only the column value
// we need to get the row value. The cast is needed due to a design oversight
// in JavaFX 8, which is fixed in newer versions. See https://bugs.openjdk.java.net/browse/JDK-8144088
ConstraintsDataModel data = ((TableRow<ConstraintsDataModel>)getTableRow())
.getItem();
combobox.disableProperty().unbind();
combobox.disableProperty().bind(data.disableProperty());
}
}
This is assuming your row data type is indeed ConstaintDataModel, I couldn't quite follow through.
Another option which may be more elegant is to use the editing property of a row - bind the combo box's disable property to the negation of the editing property of the row, and use startEdit and cancelEdit/commitEdit when you start and end editing. This way you will not have to re-bind the disable property of the combo box, as it will always refer to the correct row.
I've added a menu context item to the TreelistEx. This menu item sends a messages that I later catch in a HandleMessage method.
In this method i create a new item ( template type and parent item are given in the source of the treelist field ).
All i need now is a way to ask the user for a name. But i haven't been able to find a simple way to do this.
class MyTreeListEx : TreelistEx, IMessageHandler
{
void IMessageHandler.HandleMessage(Message message)
{
if (message == null)
{ return; }
if (message["id"] == null)
{ return; }
if (!message["id"].Equals(ID))
{ return; }
switch (message.Name)
{
case "treelist:edit":
// call default treelist code
case "mytreelistex:add":
// my own code to create a new item
}
}
}
Does anyone have any suggestions on how to achieve this ?
Edit: added image & code + i'm using Sitecore 8 Update 1
I don't know which version of Sitecore you use but what you can try is SheerResponse.Input method.
You can use it like this:
using Sitecore.Configuration;
using Sitecore.Globalization;
using Sitecore.Shell.Applications.ContentEditor.FieldTypes;
using Sitecore.Web.UI.Sheer;
void IMessageHandler.HandleMessage(Message message)
{
...
case "mytreelistex:add":
Sitecore.Context.ClientPage.Start(this, "AddItem");
break;
}
protected static void AddItem(ClientPipelineArgs args)
{
if (args.IsPostBack)
{
if (!args.HasResult)
return;
string newItemName = args.Result;
// create new item here
// if you need refresh the page:
//SheerResponse.Eval("scForm.browser.getParentWindow(scForm.browser.getFrameElement(window).ownerDocument).location.reload(true)");
}
else
{
SheerResponse.Input("Enter the name of the new item:", "New Item Default Name", Settings.ItemNameValidation,
Translate.Text("'$Input' is not a valid name."), Settings.MaxItemNameLength);
args.WaitForPostBack();
}
}
This code will even validate your new item name for incorrect characters and length.
I'm attempting to search a combobox based on text entered via a keyboard event. The search is working and the correct result is being selected but I can't seem to get the scrollToIndex to find the correct item which should be the found result (i). It's scrolling to the last letter entered which I believe is the default behavior of a combobox. I think I'm referring to the event target incorrectly. Newbie tearing my hair out. Can you help? Thank you. Here's the function:
private function textin(event:KeyboardEvent):void
{
var combo:ComboBox = event.target as ComboBox;
var source:XMLListCollection = combo.dataProvider as XMLListCollection;
str += String.fromCharCode(event.charCode);
if (str=="") {
combo.selectedIndex = 0;
}
for (var i:int=0; i<source.length; i++) {
if ( source[i].#name.match(new RegExp("^" + str, "i")) ) {
combo.selectedIndex = i;
event.target.scrollToIndex(i);
break;
}
}
}
Control:
<mx:ComboBox keyDown="textin(event);" id="thislist" change="processForm();" dataProvider="{xmllist}"/>
If event.target is a mx.control.ComboBox then it doesn't have a scrollToIndex method, which is a method defined in mx.controls.ListBase, which the ComboBox doesn't inherit from. Check the api reference for the ComboBox. What exactly is the result you a you are trying to achieve here? If you set the selected index of a ComboBox it should display the item at that index.
EDIT: Try getting replacing event.target.scrollToIndex(i) (which should throw an error anyway) and replace it with event.stopImmediatePropagation(). This should prevent whatever the default key handler is from firing and overriding your event handler.
Here is a solution, based on Kerri's code and Ryan Lynch's suggestions. The credit goes to then.
It's working for me, so I will leave the complete code here for the future generations. :)
import com.utils.StringUtils;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import mx.controls.ComboBox;
public class ExtendedComboBox extends ComboBox
{
private var mSearchText : String = "";
private var mResetStringTimer : Timer;
public function ExtendedComboBox()
{
super();
mResetStringTimer = new Timer( 1000 );
mResetStringTimer.addEventListener( TimerEvent.TIMER, function() : void { mResetStringTimer.stop(); mSearchText = ""; } );
}
override protected function keyDownHandler( aEvent : KeyboardEvent ):void
{
if( aEvent.charCode < 32 )
{
super.keyDownHandler( aEvent );
return;
}
var lComboBox : ComboBox = aEvent.target as ComboBox;
var lDataProvider : ArrayCollection = lComboBox.dataProvider as ArrayCollection;
mSearchText += String.fromCharCode( aEvent.charCode );
if ( StringUtils.IsNullOrEmpty( mSearchText ) )
{
lComboBox.selectedIndex = 0;
aEvent.stopImmediatePropagation();
return;
}
if( mResetStringTimer.running )
mResetStringTimer.reset();
mResetStringTimer.start();
for ( var i : int = 0; i < lDataProvider.length; i++ )
{
if ( lDataProvider[i].label.match( new RegExp( "^" + mSearchText, "i") ) )
{
lComboBox.selectedIndex = i;
aEvent.stopImmediatePropagation();
break;
}
}
}
}
This solution expects an ArrayCollection as the dataProvider and a field named "label" to do the searching. You can create a variable to store the name of the field, and use it like this:
lDataProvider[i][FIELD_NAME_HERE].match( new RegExp( "^" + mSearchText, "i") )
Have fun!