I have too many buttons in a table and I'd like to replace them by a button that open a dropdown list of actions. However I don't really know how to handle the events from the dropdown items. I manage to do it using a javascript function but it's not very practical because I can only pass primitive values.
I also want to make it as a custom cell in the future to use it in different pages in my project so returning some html isn't very practical.
Here's my code :
final ButtonCell buttonInfoCell = new ButtonCell();
Column<GwtStockProduct, String> buttonCell = new Column<GwtStockProduct, String>(buttonInfoCell) {
#Override
public void render(final Context context, final GwtStockProduct value, final SafeHtmlBuilder sb) {
Div div = new Div();
Div bG = new Div();
div.add(bG);
bG.addStyleName("btn-group");
Button button = new Button();
DropDownMenu dropDown = new DropDownMenu();
Span span = new Span();
span.addStyleName("caret");
span.setVisible(false);
button.add(span);
button.getElement().setAttribute("style", "background-image: none !important; background-color: #234C78 !important;");
// button.removeStyleName("");
button.addStyleName("btn-hide-icon btn-blue");
button.setDataToggle(Toggle.DROPDOWN);
button.setText("Change stock");
for (int i = 1; i < 5; ++i) {
AnchorListItem item = new AnchorListItem();
item.getElement().getFirstChildElement().removeAttribute("href");
item.getElement().getFirstChildElement().setAttribute("onclick", "triggerClick('" + i + "')");
item.setText("Item " + i);
dropDown.add(item);
}
// dropDown.getElement().setAttribute("style", "position: relative !important;");
bG.add(button);
bG.add(dropDown);
// sb.append(SafeHtmlUtils.fromTrustedString(buttonGroup));
sb.appendHtmlConstant(div.getElement().getInnerHTML());
}
#Override
public String getValue(final GwtStockProduct object) {
// TODO Auto-generated method stub
return null;
}
};
stockTable.addColumn(buttonCell, "Actions");
stockTable.setColumnWidth(buttonCell, 5, Unit.PCT);
I use SelectionCell to render a drop-down list of options. Maybe that will help you:
ArrayList<String> options = new ArrayList<String>();
options.add("choose an option..."); // the prompt text
options.add("option 1");
options.add("option 2");
// ...
final SelectionCell optionsCell = new SelectionCell(options);
Column<TableType, String> optionsColumn = new Column<TableType, String>(optionsCell) {
#Override
public String getValue(TableType object) {
return null;
}
};
optionsColumn.setFieldUpdater(new FieldUpdater<TableType, String>() {
#Override
public void update(int index, TableType object, String value) {
if(value == "option 1")
// process option 1
else if(value == "option 2")
// process option 2
// ...
// reset drop-down to show the prompt text
optionsCell.clearViewData(object);
redrawRow(index);
}
});
table.addColumn(optionsColumn, "options");
The first option is just a prompt text and after each selection change the drop-down list is reset to show the prompt.
The disadvantage is that you can not have different sets of options for different rows as the list is generated once for the whole column.
Related
I have to use a component which has autocomplete and multiselection, I will attach an image to show what I mean:
I know it is not supported by the base JavaFx but maybe you know where can I find any suggestion how to do it.
If there is any 3rd party library which has this functionality I would appreciate a link, or if doesn't then any suggestion / idea which helps me implementing it.
The autocomplete part is already implemented and answered here: JavaFX TextField Auto-suggestions so please don't suggest it. I'm interested in the multiselection part so after an element is found to be displayed in the textfield and I can look for further items.
Here is the solution which combines both the autocomplete and tagbar property.
public class AutocompleteMultiSelectionBox extends HBox {
private final ObservableList<String> tags;
private final ObservableSet<String> suggestions;
private ContextMenu entriesPopup;
private static final int MAX_ENTRIES = 10;
private final TextField inputTextField;
public AutocompleteMultiSelectionBox() {
getStyleClass().setAll("tag-bar");
getStylesheets().add(getClass().getResource("style.css").toExternalForm());
tags = FXCollections.observableArrayList();
suggestions = FXCollections.observableSet();
inputTextField = new TextField();
this.entriesPopup = new ContextMenu();
setListner();
inputTextField.setOnKeyPressed(event -> {
// Remove last element with backspace
if (event.getCode().equals(KeyCode.BACK_SPACE) && !tags.isEmpty() && inputTextField.getText().isEmpty()) {
String last = tags.get(tags.size() - 1);
suggestions.add(last);
tags.remove(last);
}
});
inputTextField.prefHeightProperty().bind(this.heightProperty());
HBox.setHgrow(inputTextField, Priority.ALWAYS);
inputTextField.setBackground(null);
tags.addListener((ListChangeListener.Change<? extends String> change) -> {
while (change.next()) {
if (change.wasPermutated()) {
ArrayList<Node> newSublist = new ArrayList<>(change.getTo() - change.getFrom());
for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
newSublist.add(null);
}
for (int i = change.getFrom(), end = change.getTo(); i < end; i++) {
newSublist.set(change.getPermutation(i), getChildren().get(i));
}
getChildren().subList(change.getFrom(), change.getTo()).clear();
getChildren().addAll(change.getFrom(), newSublist);
} else {
if (change.wasRemoved()) {
getChildren().subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear();
}
if (change.wasAdded()) {
getChildren().addAll(change.getFrom(), change.getAddedSubList().stream().map(Tag::new).collect(
Collectors.toList()));
}
}
}
});
getChildren().add(inputTextField);
}
/**
* Build TextFlow with selected text. Return "case" dependent.
*
* #param text - string with text
* #param filter - string to select in text
* #return - TextFlow
*/
private static TextFlow buildTextFlow(String text, String filter) {
int filterIndex = text.toLowerCase().indexOf(filter.toLowerCase());
Text textBefore = new Text(text.substring(0, filterIndex));
Text textAfter = new Text(text.substring(filterIndex + filter.length()));
Text textFilter = new Text(text.substring(filterIndex,
filterIndex + filter.length())); //instead of "filter" to keep all "case sensitive"
textFilter.setFill(Color.ORANGE);
textFilter.setFont(Font.font("Helvetica", FontWeight.BOLD, 12));
return new TextFlow(textBefore, textFilter, textAfter);
}
/**
* "Suggestion" specific listners
*/
private void setListner() {
//Add "suggestions" by changing text
inputTextField.textProperty().addListener((observable, oldValue, newValue) -> {
//always hide suggestion if nothing has been entered (only "spacebars" are dissalowed in TextFieldWithLengthLimit)
if (newValue.isEmpty()) {
entriesPopup.hide();
} else {
//filter all possible suggestions depends on "Text", case insensitive
List<String> filteredEntries = suggestions.stream()
.filter(e -> e.toLowerCase().contains(newValue.toLowerCase()))
.collect(Collectors.toList());
//some suggestions are found
if (!filteredEntries.isEmpty()) {
//build popup - list of "CustomMenuItem"
populatePopup(filteredEntries, newValue);
if (!entriesPopup.isShowing()) { //optional
entriesPopup.show(this, Side.BOTTOM, 0, 0); //position of popup
}
//no suggestions -> hide
} else {
entriesPopup.hide();
}
}
});
//Hide always by focus-in (optional) and out
focusedProperty().addListener((observableValue, oldValue, newValue) -> entriesPopup.hide());
}
/**
* Populate the entry set with the given search results. Display is limited to 10 entries, for performance.
*
* #param searchResult The set of matching strings.
*/
private void populatePopup(List<String> searchResult, String searchRequest) {
//List of "suggestions"
List<CustomMenuItem> menuItems = new LinkedList<>();
//Build list as set of labels
searchResult.stream()
.limit(MAX_ENTRIES) // Limit to MAX_ENTRIES in the suggestions
.forEach(result -> {
//label with graphic (text flow) to highlight founded subtext in suggestions
TextFlow textFlow = buildTextFlow(result, searchRequest);
textFlow.prefWidthProperty().bind(AutocompleteMultiSelectionBox.this.widthProperty());
CustomMenuItem item = new CustomMenuItem(textFlow, true);
menuItems.add(item);
//if any suggestion is select set it into text and close popup
item.setOnAction(actionEvent -> {
tags.add(result);
suggestions.remove(result);
inputTextField.clear();
entriesPopup.hide();
});
});
//"Refresh" context menu
entriesPopup.getItems().clear();
entriesPopup.getItems().addAll(menuItems);
}
public final ObservableList<String> getTags() {
return tags;
}
public final ObservableSet<String> getSuggestions() {
return suggestions;
}
/**
* Clears then repopulates the entries with the new set of data.
*
* #param suggestions set of items.
*/
public final void setSuggestions(ObservableSet<String> suggestions) {
this.suggestions.clear();
this.suggestions.addAll(suggestions);
}
private class Tag extends HBox {
Tag(String tag) {
// Style
getStyleClass().add("tag");
// Remove item button
Button removeButton = new Button("x");
removeButton.setBackground(null);
removeButton.setOnAction(event -> {
tags.remove(tag);
suggestions.add(tag);
inputTextField.requestFocus();
});
// Displayed text
Text text = new Text(tag);
text.setFill(Color.WHITE);
text.setFont(Font.font(text.getFont().getFamily(), FontWeight.BOLD, text.getFont().getSize()));
// Children position
setAlignment(Pos.CENTER);
setSpacing(5);
setPadding(new Insets(0, 0, 0, 5));
getChildren().addAll(text, removeButton);
}
}
}
.css
.tag-bar {
-fx-border-color: lightblue;
-fx-spacing: 3;
-fx-padding: 3;
-fx-max-height: 30;
}
.tag-bar .tag {
-fx-background-color: -fx-selection-bar;
-fx-border-radius: 5 5 5 5;
}
.tag-bar .tag {
-fx-text-fill: white;
}
.tag-bar .tag .button{
-fx-text-fill: orange;
-fx-font-weight: bold;
}
Adding to #Sunflame's answer (Sorry for Kotlin code)
Add this after the popup is created on the constructor, if you want to add a new item
inputTextField.onKeyTyped = EventHandler { event ->
if ("\r" == event.character && inputTextField.text.isNotEmpty()) {
val newTag = inputTextField.text
suggestions.add(newTag)
tags.add(newTag)
inputTextField.text = ""
}
}
Thank you for the hard work, this is just a minor feature added
Iteresiting case which make my work a nightmare in GWT. I try to add to my panel tab with dynamic changed table to add some params by user. Requirements are dynamic adding new elements and that user can overview all of them and eventually correct it.
I create all that with callTable but in one browser (Chromium or Opera) I can add new elements but in the same time on the same package in mozzila I see scroll bar but canot add dynamically new elements, but if I erase some preloaded one new one appears. Can someone explain to me what exactly goes wrong as it is one package run in incognito mode with erased history and cookies after every session
Maybe screen will be helpfull- param fill added dynamically:
view with new element 1
table with scrool bar but without option of adding new element 2
And source code:
//initial layout and component
final VLayout customLayout = new VLayout();
Canvas customComponent = new Canvas();
//dataprovider modal creation with in memory list
final ListDataProvider<String> model = new ListDataProvider<>(
getProvisioningParameterList());
final CellTable<String> table = new CellTable<>();
// create column with data
Column<String, String> nameColumn = new Column<String, String>(new EditTextCell()) {
#Override
public String getValue(String object) {
return object;
}
};
//column with delete button
Column<String, String> deleteBtn = new Column<String, String>(
new ButtonCell()) {
#Override
public String getValue(String object) {
return "x";
}
};
// add column to the table
table.addColumn(nameColumn, "Custom parameters");
table.addColumn(deleteBtn, "Click to delete row");
//initialize table row size
table.setWidth("100%");
// Set the field updater, whenever user clicks on button row will be removed.
deleteBtn.setFieldUpdater(new FieldUpdater<String, String>() {
#Override
public void update(int index, String object, String value) {
model.getList().remove(object);
model.refresh();
table.redraw();
}
});
// add a data display to the table which is adapter.
model.addDataDisplay(table);
//add Button
final IButton addButton = new IButton("Add");
addButton.setIcon("icons/add.png");
addButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
model.getList().add("fill");
model.refresh();
table.redraw();
}
});
//add custom config panel with proper view
ScrollPanel sp = new ScrollPanel();
sp.setAlwaysShowScrollBars(true);
customComponent.setContents("Params");
customComponent.setAutoHeight();
customLayout.setMargin(15);
customLayout.addMember(customComponent);
customLayout.addMember(addButton, 1);
customLayout.addMember(saveButton, 2);
customLayout.addMember(table);
If someone will still wonder about it I got a solution. I forgot to add ScrollPanel to layout. Edit Additionally I put table into scrollPanel in specific dimentions and that two element structure to main VLayout. Now it works as expected :)
I have a combo viewer based on a CCombo:
public static ComboViewer createComboViewer(Composite parent) {
CCombo combo = new CCombo(parent, SWT.BORDER);
combo.setTextLimit(2);
combo.addVerifyListener(new UpperCaseKeyListener());
ComboViewer viewer = new ComboViewer(combo);
viewer.setContentProvider(ArrayContentProvider.getInstance());
viewer.setLabelProvider(new CustomLabelProvider());
String[] strings = {"AB","CD","EF","GH","IJ"};
viewer.getCCombo().addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent keyEvent) {
String key = viewer.getCCombo().getText();
System.out.println(key);
String[] items = viewer.getCCombo().getItems();
if (!key.equals("") && key.length()==2) {
for (int i=0;i<strings.length;i++) {
if (strings[i].contains(key)) {
final ISelection selection = new StructuredSelection(strings[i]);
viewer.setSelection(selection);
}
}
}
}
});
I have a list of strings : {"AB","CD","EF","GH","IJ"} in this combo viewer.
When I type for example "AB" my item is selected from the drop-down list , but it is not highlighted with blue.
How can I make this happen?
I want that when I type an item in the combo and it is found in the list , to be highlighted with blue when I open the drop down list.
You must call setInput on the viewer to tell it about your content:
String[] strings = {"AB","CD","EF","GH","IJ"};
viewer.setInput(string);
In general a viewer requires both the content provider and label provider to be set and then setInput to be called.
I want to create listbox.and when user clicks on that i want to display datagrid into dropdown.
private DataGrid objDataGrid;
public CallDataGrid() {
// TODO Auto-generated constructor stub
}
public CallDataGrid(ArrayList<Student> objArrayList) {
System.out.println("Datagrid is now going to set.");
objDataGrid = new DataGrid<Student>();
objLayoutPanel = new SimpleLayoutPanel();
objScrollPanel = new ScrollPanel();
objScrollPanel.add(objDataGrid);
objLayoutPanel.add(objScrollPanel);
objDataGrid.setEmptyTableWidget(new Label("There is no data to display."));
final TextColumn<Student> nameColumn = new TextColumn<Student>() {
#Override
public String getValue(Student object) {
return object.getStrName();
}
};
objDataGrid.addColumn(nameColumn, "User Name");
objDataGrid.setColumnWidth(nameColumn,100,Unit.PX);
final TextColumn<Student> passwordColumn = new TextColumn<Student>() {
#Override
public String getValue(Student object) {
return object.getStrPassword();
}
};
objDataGrid.addColumn(passwordColumn, "Password");
objDataGrid.setColumnWidth(passwordColumn,90,Unit.PX);
objDataGrid.setWidth("190px");
objDataGrid.setHeight("100px");
objDataGrid.setRowData(0, objArrayList);
objDataGrid.setPageSize(5);
// now how can i set this datagrid into Listbox or suggestion box??
No direct way to do what you want. Instead of a ListBox widget you can use a TextBox with a button on the right. When clicking the button put your DataGrid into a PopupPanel and display it by setting the its position on the bottom of the TextBox.
I have basically two pairs of widgets, one has two datepickers and a submit button that submits the dates to an RPC on the server, and the other is a map which displays information between those dates. Each of them work individually, but when the map is displayed I cannot do anything to the datepickers or the submit button.
Here is the code that matters
public void onModuleLoad() {
final DockLayoutPanel dock = new DockLayoutPanel(Unit.PCT);
/*
* Asynchronously loads the Maps API.
*
* The first parameter should be a valid Maps API Key to deploy this
* application on a public server, but a blank key will work for an
* application served from localhost.
*/
Maps.loadMapsApi("", "2", false, new Runnable() {
public void run() {
final FormPanel form = new FormPanel();
form.setMethod(form.METHOD_POST);
DatePicker datePicker = new DatePicker();
final Label text = new Label();
text.setText("Start Date");
// Set the value in the text box when the user selects a date
datePicker.addValueChangeHandler(new ValueChangeHandler<Date>() {
public void onValueChange(ValueChangeEvent<Date> event) {
Date date = event.getValue();
String dateString = DateTimeFormat.getMediumDateFormat().format(date);
text.setText("Start Date - " + dateString);
}
});
// Set the default value
datePicker.setValue(new Date(), true);
// Add the widgets to the page
RootPanel.get().add(text);
RootPanel.get().add(datePicker);
DatePicker datePicker2 = new DatePicker();
final Label text2 = new Label();
text2.setText("End Date");
// Set the value in the text box when the user selects a date
datePicker2.addValueChangeHandler(new ValueChangeHandler<Date>() {
public void onValueChange(ValueChangeEvent<Date> event) {
Date date = event.getValue();
String dateString = DateTimeFormat.getMediumDateFormat().format(date);
text2.setText("End Date - " + dateString);
}
});
// Set the default value
datePicker2.setValue(new Date(), true);
// Add the widgets to the page
RootPanel.get().add(text2);
RootPanel.get().add(datePicker2);
RootPanel.get().add(new Button("Submit", new ClickListener()
{
public void onClick(Widget sender)
{
form.submit();
}
}));
form.addSubmitHandler(new SubmitHandler() {
#Override
public void onSubmit(SubmitEvent event) {
getAwards(text.getText(),text2.getText());
}
});
}
});
}
This is the code that creates the datepickers.
Here is the code that displays the map.
private void buildUi() {
ArrayList<Icon> icons = createIconList();
content = new ArrayList<String>();
LatLng sanDiego = LatLng.newInstance(32.83049, -117.122717);
final MapWidget map = new MapWidget(sanDiego, 9);
map.clearOverlays();
map.setSize("100%", "100%");
// Add some controls for the zoom level
map.addControl(new LargeMapControl());
java.util.Random rand = new java.util.Random();
for(ContractAward ca : sanDiegoAwards)
{
double off1 = (rand.nextDouble()-.5)/100;
double off2 = (rand.nextDouble()-.5)/100;
index++;
// Open a map centered on San Diego
LatLng contract = LatLng.newInstance(ca.getLat() + off1,ca.getLon()+off2);
MarkerOptions mo = MarkerOptions.newInstance();
mo.setTitle(""+index);
mo.setIcon(icons.get(whichIcon(ca.getAmount())));
final Marker mark = new Marker(contract,mo);
map.addOverlay(mark);
String caContent = "<P>Company: " + ca.getCompany() + "<br>";
caContent+= "Date: " + ca.getDate().toGMTString() + "<br>";
caContent+= "Amount: " + ca.getAmount() + "<br>";
caContent+= "ContractID: " + ca.getContractID() + "</P>";
content.add(caContent);
mark.addMarkerClickHandler(new MarkerClickHandler() {
public void onClick(MarkerClickEvent event) {
InfoWindow info = map.getInfoWindow();
info.open(mark, new InfoWindowContent(content.get(Integer.parseInt(mark.getTitle())-1)));
}
});
}
final DockLayoutPanel dock = new DockLayoutPanel(Unit.PCT);
dock.addEast(map, 80);
// Add the map to the HTML host page
RootLayoutPanel.get().add(dock);
}
I've tried changing the RootLayoutPanel.get().add(dock) to RootPanel.get().add(dock) but then the map itself does not display. I've also tried changing all the top parts to be docked and inserted via rootlayoutpanel but the same issue arises as is currently the problem.
Several possibilities I see on a first glance.
1) You are adding the datepicker stuff to RootPanel, but the map stuff to RootLayoutPanel. I suggest sticking to RootLayoutPanel for both, if it works, standards mode generally has more useful, up-to-date stuff in GWT.
2) Why are you doing that whole thing with Runnable in the first bit of code? Is there a reason all that stuff isn't in just onModuleLoad?
Solution ended up being to use RootLayoutPanel and combine the two docks that take up the east and west portions of the screen into 1 dock that takes the entire screen. Having two docks led to only the most recently added one being active, but putting the two docks into 1 lets both of them function.