The ClassChoice control inherits CheckBoxMultipleChoice. It is a common control used on multiple pages with selections persisted in the session. The available choices are obtained from the database. An "All" checkbox is added when there is more than one data item. On some pages a selection change causes the page to be refreshed with new data. On other page the selections should change without a refresh.
My problem is that I need to control the "All" checkbox when other checkboxes change and to change all of the checkboxes when the "All" checkbox changes.
I tried to call updateModel() to force a change but that did not work. How can I change the selections (the model parameter) without refreshing the page?
This edited code does not show page refreshing.
public class ClassChoice<T> extends CheckBoxMultipleChoice
{
private static final long serialVersionUID = 1L;
#SpringBean
private ClassService classService;
List<EntityClassModel> selection;
EntityClassModel ecmAll;
static List<EntityClassModel> availableClasses;
public ClassChoice(..)
{
super("classcheckboxes");
setSuffix(" "); // sets checkbox separator and ensures inline display
ecmAll = (EntityClassModel) modelFactory.getNewClassModel();
ecmAll.setClassname("All");
// List of all classes associated with user
availableClasses = classService.getListOfClasses(..);
setClassChoices();
add( new AjaxFormChoiceComponentUpdatingBehavior()
{
private static final long serialVersionUID = 1L;
#Override
protected void onUpdate(AjaxRequestTarget target)
{
List<Integer> previousIDs = UserSession.get().getSelectedClassIDs();
if ((previousIDs.size() > 0) && ((previousIDs.size() + 1) >= availableClasses.size()))
{
// Was previously Select All
if (selection.get(selection.size() - 1) == ecmAll)
{
// Select All still selected, remove it
selection.remove(selection.size() - 1);
}
else
{
// Remove all selections
selection.clear();
}
}
else if (selection.size() > 0)
{
// Was none or some selected
if (selection.get(selection.size() - 1) == ecmAll)
{
// Select All, select all available
selection.clear();
selection.addAll(availableClasses);
}
else if ((selection.size() + 1) >= availableClasses.size())
{
// Is now full, add Select All
selection.add(ecmAll);
}
// else change but no special handling required
}
// else none are currently selected
UserSession.get().setSelectedClasses(selection);
// Generate a list of selected class IDs, excluding All
List<Integer> selectedIDs = new ArrayList<Integer>();
int copysize = selection.size();
if ((copysize > 0) && (selection.get(copysize - 1) == ecmAll))
{
copysize--;
}
for (int index = 0; index < copysize; index++)
{
selectedIDs.add(selection.get(index).getId());
}
UserSession.get().setSelectedClassIDs(selectedIDs);
// Update the selections on the page
updateModel();
}
});
Initialize();
}
#SuppressWarnings("unchecked")
protected void Initialize()
{
// Grabs already selected classes from UserSession
List<Integer> selectedIDs = UserSession.get().getSelectedClassIDs();
selection = classService.getClassesByClassIDs(selectedIDs);
if (selectedIDs.size() > 1)
{
if ((selectedIDs.size() + 1) >= availableClasses.size())
{
selection.add(ecmAll);
}
}
setModel(Model.ofList(selection));
// Configure the data and display
setChoiceRenderer(new ChoiceRenderer<EntityClassModel>("classname", "id"));
setOutputMarkupId(true);
}
#SuppressWarnings("unchecked")
public void setClassChoices()
{
// Adds 'All' option when there is more than one class
if (availableClasses.size() > 1)
{
availableClasses.add(ecmAll);
}
setChoices(availableClasses);
}
public List<EntityClassModel> getSelection()
{
return selection;
}
}
You have to use the AjaxRequestTarget to update the HTML element on the browser side. By adding/removing elements to selection you change the model of ClassChoice at the server side. At the bottom of AjaxFormChoiceComponentUpdatingBehavior#onUpdate() you should do target.add(this) to tell Wicket to repaint this ClassChoice instance with its new selection/model.
Make sure you call setOutputMarkupId(true) in its constructor because otherwise you won't be able to update it with Ajax.
Related
I would to ask why does IndexOutOfBoundsException appear when I try to delete the first row from the table view of supplement which is index 0. I am using a button to delete the row
Update: update the code to have a minimal reproducible example
SupplementTest.java
public class SupplementTest extends Application {
WindowController windowGUI = new WindowController();
Stage stageGUI;
Scene sceneGUI;
#Override
public void start(Stage primaryStage) throws IOException {
FXMLLoader assignment2 = new FXMLLoader(getClass().getResource("SupplementFXML.fxml"));
Parent fxmlFile = assignment2.load();
try {
stageGUI = primaryStage;
windowGUI.initialize();
sceneGUI = new Scene(fxmlFile, 250, 350);
stageGUI.setScene(sceneGUI);
stageGUI.setTitle("Supplement");
stageGUI.show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
WindowController.java
public class WindowController {
Stage newWindow = new Stage();
boolean deleteSupplement;
#FXML
private GridPane primaryGrid = new GridPane();
#FXML
private Label supplementLabel = new Label();
#FXML
private Button deleteBtn = new Button(), addBtn = new Button();
public TableView<Supplement> supplementView = new TableView<>();
int suppIndex;
ArrayList<Supplement> supplementList = new ArrayList<>();
// initialize Method
public void initialize() {
newWindow.initModality(Modality.APPLICATION_MODAL);
newWindow.setOnCloseRequest(e -> e.consume());
initializeWindow();
updateSupplementList();
}
public void initializeWindow() {
deleteSupplement = false;
TableColumn<Supplement, String> suppNameColumn = new TableColumn<>("Name");
suppNameColumn.setCellValueFactory(new PropertyValueFactory<>("supplementName"));
TableColumn<Supplement, Double> suppCostColumn = new TableColumn<>("Weekly Cost");
suppCostColumn.setCellValueFactory(new PropertyValueFactory<>("weeklyCost"));
supplementView.getColumns().addAll(suppNameColumn, suppCostColumn);
supplementView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
suppIndex = supplementView.getSelectionModel().getSelectedIndex();
addBtn.setOnAction(e -> {
supplementList.add(new Supplement("Test1", 10));
supplementList.add(new Supplement("Test2", 20));
supplementList.add(new Supplement("Test3", 15));
updateSupplementList();
});
// remove button
deleteBtn.setOnAction(e -> {
deleteSupplement = true;
deleteSupplement();
});
}
public void updateSupplementList() {
supplementView.getItems().clear();
if (supplementList.size() > 0) {
for(int i = 0; i < supplementList.size(); i++) {
Supplement supplement = new Supplement(supplementList.get(i).getSupplementName(),
supplementList.get(i).getWeeklyCost());
supplementView.getItems().add(supplement);
}
}
}
public void deleteSupplement() {
try {
ObservableList<Supplement> supplementSelected, allSupplement;
allSupplement = supplementView.getItems();
supplementSelected = supplementView.getSelectionModel().getSelectedItems();
supplementSelected.forEach(allSupplement::remove);
supplementList.remove(suppIndex);
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
Supplement.java
public class Supplement implements Serializable {
private String supplementName;
private double weeklyCost;
public Supplement() {
this.supplementName = "";
this.weeklyCost = 0.00;
}
public Supplement(String suppName, double weeklyCost) {
this.supplementName = suppName;
this.weeklyCost = weeklyCost;
}
public String getSupplementName() {
return supplementName;
}
public double getWeeklyCost() {
return weeklyCost;
}
public void setSupplementName(String supplementName) {
this.supplementName = supplementName;
}
public void setWeeklyCost(double weeklyCost) {
this.weeklyCost = weeklyCost;
}
}
How do I fix it so that when I delete any index in the table view the IndexOutOfBoundsException does not appear?
It's difficult to know for certain what is causing the exception, because your code is both incomplete (so no-one here can copy, paste, and run it to reproduce the error), and very confusing (it is full of seemingly-unnecessary code). However:
You seem to be doing two different things to delete the selected item(s) from the table:
supplementSelected = supplementView.getSelectionModel().getSelectedItems();
supplementSelected.forEach(allSupplement::remove);
which is an attempt to delete all selected items (though I don't believe it will work if more than one item is selected)
and
supplementList.remove(suppIndex);
which will delete the selected item, as defined by the selected index property in the selection model. (It is the currently selected item in a single selection model, or the last selected item in a multiple selection model, or -1 if nothing is selected.)
The latter will not work, because you only ever set suppIndex in your initialization code:
public void initializeWindow() {
// ...
suppIndex = supplementView.getSelectionModel().getSelectedIndex();
// ...
}
Of course, when this code is executed, the user has not had a chance to selected anything (the table isn't even displayed at this point), so nothing is selected, and so suppIndex is assigned -1. Since you never change it, it is always -1, and so when you call
supplementList.remove(suppIndex);
you get the obvious exception.
If you are only supporting single selection, and want to delete the currently selected item (or the last selected item in multiple selection), just get the selection at the time. You probably still want to check something is selected:
public void deleteSupplement() {
int selectedIndex = supplementView.getSelectionModel().getSelectedIndex();
if (selectedIndex >= 0) {
supplementView.getItems().remove(selectedIndex);
}
}
A slight variation on this, which I think is preferable, is to work with the actual object instead of its index:
public void deleteSupplement() {
Supplement selection = supplementView.getSelectionModel().getSelectedItem();
if (selection != null) {
supplementView.getItems().remove(selection);
}
}
Now, of course (in a theme that is common to a lot of your code), you can remove suppIndex entirely; it is completely redundant.
If you want to support multiple selection, and delete all selected items, then the code you currently have for that will cause an issue if more than one item is selected. The problem is that if a selected item is removed from the table's items list, it will also be removed from the selection model's selected items list. Thus, the selected items list (supplementSelected) in your code changes while you are iterating over it with forEach(...), which will throw a ConcurrentModificationException.
To avoid this, you should copy the list of selected items into another list, and remove those items:
public void deleteSupplement() {
List<Supplement> selectedItems
= new ArrayList<>(supplementView.getSelectionModel().getSelectedItems());
supplementView.getItems().removeAll(selectedItems);
}
Of course, this code also works with single selection (when the list is always either length 0 or length 1).
To address a couple of other issues: there is really no point in keeping a separate list of Supplement items. The table already keeps that list, and you can reference it at any time with supplementView.getItems(). (If you wanted to reference the list elsewhere, e.g. in a model in a MVC design, you should make sure that there is just a second reference to the existing list; don't create a new list.)
In particular, you should not rebuild the table entirely from scratch every time you add a new item to the list. Get rid of the redundant supplementList entirely from your code. Get rid of updateSupplementList() entirely; it is firstly doing way too much work, and secondly (and more importantly) will replace all the existing items just because you add a new one. This will lost important information (for example it will reset the selection).
To add new items, all you need is
addBtn.setOnAction(e -> {
supplementView.getItems().add(new Supplement("Test1", 10));
supplementView.getItems().add(new Supplement("Test2", 20));
supplementView.getItems().add(new Supplement("Test3", 15));
});
There are various other parts of your code that don't make any sense, such as:
The deleteSupplement variable. This seems to have no purpose.
The try-catch in the deleteSupplement method. The only exceptions that can be thrown here are unchecked exceptions caused by programming logic errors (such as the one you see). There is no point in catching those; you need to fix the errors so the exceptions are not thrown.
The #FXML annotations. You should never initialize fields that are annotated #FXML. This annotation means that the FXMLLoader will initialize these fields. In this case (as far as I can tell) these are not even associated with an FXML file at all, so the annotation should be removed.
I am using MVP pattern. In my View, I am using TextBox named uiTextBox in which I have added my enum class Message Status using following code in the method initStaticContent.
private void initStaticContent() {
for (MessageStatus status : EnumSet.allOf(MessageStatus.class))
uiTextStatus.setText(status.name());
updateUiWithPermissions();
}
My Enum Class is:
public enum MessageStatus {
SENDING, SENT, FAILED;
}
And in my updateUiWithModel() I am performing following steps :
private void updateUiWithModel() {
if (model == null)
throw new IllegalStateException("A model must be defined to update the ui.");
uiTextCreatedOn.setText(model.createdOn);
uiTextBusinessName.setText(model.businessName);
uiTextSenderStaffFirstName.setText(model.senderStaffFirstName);
selectStatus(model.status);
case SEND:{
uiTextRecipientEmail.setVisible(true);
uiTextRecipientSms.setVisible(false);
uiTextRecipientVoice.setVisible(false);
}break;
case SENDING:{
uiTextRecipientEmail.setVisible(false);
uiTextRecipientSms.setVisible(true);
uiTextRecipientVoice.setVisible(false);
}break;
case SENT:{
uiTextRecipientEmail.setVisible(false);
uiTextRecipientSms.setVisible(false);
uiTextRecipientVoice.setVisible(true);
}break;
}
public void selectStatus(MessageStatus status) {
if(status == null){
uiTextStatus.setText("");
return;
}
for(int i=0; i < uiTextStatus.getMaxLength(); i++){
String currentStatus = (uiTextStatus.getText(i));
if (currentStatus == status.name()) {
uiListBoxMethod.setSelectedIndex(i);
break;
}
}
}
Firstly, I want to know how to iterate in for each over uiTextBox and count the items in the textbox.
Secondly, if I use Listbox instead of TextBox and I want to have switch case in the Listbox how can I do it in method updateUiWithModel.
Well you can create a TextFieldStatusManipulator class that sets text and visibility state.
class TextFieldStatusManipulator {
private final Function<Model,String> extractString;
private final Predicate<Status> setVisible;
private final TextBox textBox;
public TextFieldStatusManipulator(TextBox box, Function<Model,String> ex, Predicate<Status> sv) {
textBox = box; extractString = ex; setVisible = sv;
}
public void statusChange(Model model) {
textBox.setText(extractString.apply(model));
textBox.setVisible(setVisible.test(model.status));
}
}
Then you create one for each TextField, like so
TextFieldStatusManipulator createdOnManip = new TextFieldStatusManipulator (uiTextCreatedOn, Model::getCreatedOn, s -> s == Status.SEND);
Then you add them to a list and iterate the list on status changes.
manipulatorList.forEach(m -> m.statusChange(model));
At first my question is how to let a newly added row flash in JavaFx, then I went through a lot of questions related to this topic (such as javafx: table row flashing). Most of them are using setRowFactory and override the updateItem method by adding a Timeline animation which change the state of pseudoClass of the row.
Below is my code, I am trying to building a FlashControl which can be reused.
public class TableFlashControl<T> {
private PseudoClass flashHighlight = PseudoClass.getPseudoClass("flash-highlight");
private List<T> newAdded = new ArrayList<>();
private boolean isFilterApplied = false;
private boolean isSorted = false;
public void setIsFilterApplied(boolean isFilterApplied) {
this.isFilterApplied = isFilterApplied;
}
public void add(TableView<T> table){
ListChangeListener<T> change = c -> {
while (c.next()) {
if (c.wasAdded()) {
List<? extends T> added = c.getAddedSubList();
T lastAdded = added.get(0);
if (!isFilterApplied) {
newAdded.add(lastAdded);
}
}
}
};
table.getItems().addListener(change);
table.setRowFactory(param -> new TableRow<T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
return;
}
if (newAdded.contains(item)) {
if (isSorted) {
new Thread(()->{
Timeline flasher = new Timeline(
new KeyFrame(Duration.seconds(0.4), e -> pseudoClassStateChanged(flashHighlight, true)),
new KeyFrame(Duration.seconds(0.8), e -> pseudoClassStateChanged(flashHighlight, false))
);
flasher.setCycleCount(2);
flasher.play();
}).start();
if (item == newAdded.get(0)) {
newAdded.clear();
isSorted = false;
}
}else{
if(item == newAdded.get(0)){
isSorted = true;
}
}
}
}
});
}
}
Here ListChangeListener is associated with table.getItems() which helps me to record the newly inserted row.
It is possible to add multiple rows within one operation which means newAdded.size() can be larger than 1. What's more, rows are inserted from the top of the tableView(because I sort it with the Number.)
In tableView, not all rows are visible and updateItem methods only update those visible rows. My problem comes when these two situations happen(see below).
The first scenario
In first scenario, only 4 rows are visible, if user inserts 5 rows within one time, I cannot record the last row update(the updateItem won't be called for the new_row_5). Thereby, I cannot clear newAdded list (by doing newAdded.clear())
The second scenario
In the second scenario, only 4 rows are visible again. However, there are invisible rows both at top and bottom of the visible rows. If user inserts 2 rows, one will be visible and the other will be invisible. In my case, new_row_2 will flash while new_row_1 remains invisible. If user scrolls up the tableView when new_row_2 is flashing, he will see new_row_2 is flashing while new_row_1 is not which is really weird.
I also want to know if there is any way to find the number of visible rows.
I am still new to JavaFx and I don't know if this method is good or not. I hope someone can help me fix my problems. Thanks a lot!
Your approach doesn't seem like a clean way to do this. The animation depends on the TableRow the item is positioned in and does not seem to support multiple animations happening at the same time. Furthermore it relies on the equals method of the item class not being overridden and on the user not adding a item multiple times to the TableView. Also you potentially create a large number of Timelines (not necessary to start them from a seperate thread btw, since Timeline.play() does not block).
It's better to make the animation depend on the indices. Also keeping track of the TableRows created allows you to access existing cells, should they be be assigned a index that needs to be animated. Also you could handle the animations using a single AnimationTimer by storing the data in a suitable data structure.
Also it would IMHO be most convenient to use the rowFactory class to implement this logic.
The following example makes the rows flash whether they are on-screen or not.
public class FlashTableRowFactory<T> implements Callback<TableView<T>, TableRow<T>> {
private final static PseudoClass FLASH_HIGHLIGHT = PseudoClass.getPseudoClass("flash-highlight");
public FlashTableRowFactory(TableView<T> tableView) {
tableView.getItems().addListener((ListChangeListener.Change<? extends T> c) -> {
while (c.next()) {
if (c.wasPermutated()) {
int from = c.getFrom();
int to = c.getTo();
permutationUpdate(scheduledTasks, c, from, to);
permutationUpdate(unscheduledTasks, c, from, to);
}
if (c.wasReplaced()) {
addRange(c.getFrom(), c.getTo());
} else if (c.wasRemoved()) {
int from = c.getFrom();
int removed = c.getRemovedSize();
removeRange(scheduledTasks, from, from + removed);
removeRange(unscheduledTasks, from, from + removed);
modifyIndices(unscheduledTasks, from, -removed);
modifyIndices(scheduledTasks, from, -removed);
} else if (c.wasAdded()) {
int from = c.getFrom();
int to = c.getTo();
modifyIndices(unscheduledTasks, from, to - from);
modifyIndices(scheduledTasks, from, to - from);
addRange(from, to);
}
}
// remove all flashTasks that are older than the youngest for a
// given index
Set<Integer> indices = new HashSet<>();
removeDuplicates(unscheduledTasks, indices);
removeDuplicates(scheduledTasks, indices);
flashingIndices.clear();
updateFlash(lastUpdate);
refreshFlash();
if (!unscheduledTasks.isEmpty()) {
flasher.start();
}
});
this.tableView = tableView;
}
private static void removeDuplicates(List<FlashTask> list, Set<Integer> found) {
for (Iterator<FlashTask> iterator = list.iterator(); iterator.hasNext();) {
FlashTask next = iterator.next();
if (!found.add(next.index)) {
iterator.remove();
}
}
}
private static void modifyIndices(List<FlashTask> list, int minModify, int by) {
for (FlashTask task : list) {
if (task.index >= minModify) {
task.index += by;
}
}
}
private void addRange(int index, int to) {
for (; index < to; index++) {
unscheduledTasks.add(new FlashTask(index));
}
}
private static void removeRange(List<FlashTask> list, int from, int to) {
for (Iterator<FlashTask> iterator = list.iterator(); iterator.hasNext();) {
FlashTask next = iterator.next();
if (next.index >= from && next.index < to) {
iterator.remove();
}
}
}
private static void permutationUpdate(List<FlashTask> list, ListChangeListener.Change<?> c, int from, int to) {
for (FlashTask task : list) {
if (task.index < to && task.index >= from) {
task.index = c.getPermutation(task.index);
}
}
}
// set of item indices that should flash
private final Set<Integer> flashingIndices = new HashSet<>();
// references to every row created by this factory
private final List<SoftReference<TableRow<T>>> rows = new LinkedList<>();
// tasks waiting to be scheduled
private final List<FlashTask> unscheduledTasks = new LinkedList<>();
// tasks currently being animated
private final List<FlashTask> scheduledTasks = new LinkedList<>();
private static class FlashTask {
int index;
long schedulingTime;
public FlashTask(int index) {
this.index = index;
}
}
private final TableView<T> tableView;
private long lastUpdate;
/**
* Updates flashingIndices set
* #param now the current timestamp
* #return true if the set has been modified, otherwise false.
*/
private boolean updateFlash(long now) {
boolean modified = false;
for (Iterator<FlashTask> iterator = scheduledTasks.iterator(); iterator.hasNext();) {
FlashTask next = iterator.next();
// running time in seconds
double runningTime = (now - next.schedulingTime) / (1000d * 1000d * 1000d);
// slows down the animation for demonstration
final double animationSpeed = 0.1;
if (runningTime < 0.4 / animationSpeed) {
// no need to handle tasks that run for less than 0.4 seconds
break;
} else if (runningTime > 1.6 / animationSpeed) {
// end of task reached
iterator.remove();
modified |= flashingIndices.remove(next.index);
} else if (runningTime > 0.8 / animationSpeed && runningTime < 1.2 / animationSpeed) {
// second "inactive" interval during animation
modified |= flashingIndices.remove(next.index);
} else {
// activate otherwise
modified |= flashingIndices.add(next.index);
}
}
return modified;
}
private final AnimationTimer flasher = new AnimationTimer() {
#Override
public void handle(long now) {
lastUpdate = now;
// activate waiting flash tasks
for (FlashTask task : unscheduledTasks) {
task.schedulingTime = now;
}
scheduledTasks.addAll(unscheduledTasks);
unscheduledTasks.clear();
if (updateFlash(now)) {
refreshFlash();
if (scheduledTasks.isEmpty()) {
// stop, if there are no more tasks
stop();
}
}
}
};
/**
* Sets the pseudoclasses of rows based on flashingIndices set
*/
private void refreshFlash() {
for (Iterator<SoftReference<TableRow<T>>> iterator = rows.iterator(); iterator.hasNext();) {
SoftReference<TableRow<T>> next = iterator.next();
TableRow<T> row = next.get();
if (row == null) {
// remove references claimed by garbage collection
iterator.remove();
} else {
row.pseudoClassStateChanged(FLASH_HIGHLIGHT, flashingIndices.contains(row.getIndex()));
}
}
}
#Override
public TableRow<T> call(TableView<T> param) {
if (tableView != param) {
throw new IllegalArgumentException("This factory can only be used with the table passed to the constructor");
}
return new FlashRow();
}
private class FlashRow extends TableRow<T> {
{
rows.add(new SoftReference<>(this));
}
#Override
public void updateIndex(int i) {
super.updateIndex(i);
// update pseudoclass based on flashingIndices set
pseudoClassStateChanged(FLASH_HIGHLIGHT, flashingIndices.contains(i));
}
}
}
I would like to create breadcrump on my page, but I am unable to do it by following wicket examples.. I need to extend BreadCrumbPanel when I also need to extend WebPage..
I want breadcrumb to be visible only on this specific page, after clicking some of links on the page(they call setresponsepage with some parameters) I would like to append my breadcrumb
public final class GroupMainPage extends WebPage {
// Map<String, Group> hostGroup = PropertiesLoader.getInstance().getHostGroupMap();
List<Group> hostGroup = PropertiesLoader.getInstance().getHostGroupList();
public GroupMainPage() {
super();
HostGroupManager.getInstance();
createMainTable(hostGroup);
add(new Label("message", "OK \n ok"));
}
then I have some methods which help me create table on my page, especially this one with links where I would like to append my breadcrumb:
ListView<Component> list = new ListView<Component>("groupListElement", ((Group) component).getDependentGroups()) {
#Override
protected void populateItem(ListItem<Component> item) {
final Component c = item.getModelObject();
Link link = new Link("groupLink") {
#Override
public void onClick() {
Group g = (Group) c;
if (g.getDependentGroups() != null && g.getDependentGroups().size() > 0) {
setResponsePage(new GroupMainPage(g.getDependentGroups()));
} else {
setResponsePage(new GroupServiceListPage(g.createServicesList()));
}
}
};
link.add(new Label("linkLabel", c.getName()));
item.add(link);
How to make it possible?
Is there any way to add clickHandlers (or any type of handler) to the headers of the columns in a CellTable? I want to add some sorting functionality to my CellTable and I dont see any methods in the Column or Header classes that will allow this. I used this post to figure out how to use the CellTable.
Workaround for click events:
Header<String> columnHeader = new Header<String>(new ClickableTextCell()) {
#Override
public String getValue() {
return columnName;
}
};
columnHeader.setUpdater(new ValueUpdater<String>() {
#Override
public void update(String value) {
Window.alert("Header clicked!");
}
});
table.addColumn(column, columnHeader);
There is no out of the box way of supporting sort as yet on the CellTable. However there is a manual workaround involving a lot of code drudgery. Refer the classes SortableHeader and SortableColumn in the bike shed under expenses sample. You will find the usage in com.google.gwt.sample.expenses.gwt.client.ExpenseDetails. You can use this until something concrete comes out in the next release.
check out directory: http://google-web-toolkit.googlecode.com/svn/trunk/bikeshed
With the final release of GWT 2.1, has there been any support for sortable columns added to the CellTable? Or is it still a roll your own solution after looking at the bikeshed example?
CellTable<Contact> table = new CellTable<Contact>();
// Create name column.
final TextColumn<Contact> nameColumn = new TextColumn<Contact>() {
#Override
public String getValue(Contact contact) {
return contact.name;
}
};
// Create a data provider.
ListDataProvider<Contact> dataProvider = new ListDataProvider<Contact>();
// Connect the table to the data provider.
dataProvider.addDataDisplay(table);
final List<Contact> list = dataProvider.getList();
for (Contact contact : CONTACTS) {
list.add(contact);
}
final ListHandler<Contact> columnSortHandler = new ListHandler<Contact>(
list);
Header<String> columnHeader = new Header<String>(new ClickableTextCell()) {
#Override
public String getValue() {
return "Name";
}
};
columnHeader.setUpdater(new ValueUpdater<String>() {
#Override
public void update(String value) {
if (Window.confirm("Want to do?")){
nameColumn.setSortable(true);
columnSortHandler.setComparator(nameColumn,
new Comparator<Contact>() {
public int compare(Contact o1, Contact o2) {
if (o1 == o2) {
return 0;
}
// Compare the name columns.
if (o1 != null) {
return (o2 != null) ? o1.name.compareTo(o2.name) : 1;
}
return -1;
}
});
} else nameColumn.setSortable(false);
}
});
// Make the name column sortable.
nameColumn.setSortable(false);
// Create address column.
TextColumn<Contact> addressColumn = new TextColumn<Contact>() {
#Override
public String getValue(Contact contact) {
return contact.address;
}
};
// Add the columns.
table.addColumn(nameColumn, columnHeader);
table.addColumn(addressColumn, "Address");
// Add the data to the data provider, which automatically pushes it to the
// widget.
// Add a ColumnSortEvent.ListHandler to connect sorting to the
// java.util.List.
//------------------ Code to add --------------------------------//
VerticalPanel vp = new VerticalPanel();
table.addColumnSortHandler(columnSortHandler);
//------------------ Code end --------------------------------//
// We know that the data is sorted alphabetically by default.
table.getColumnSortList().push(nameColumn);
// Add it to the root panel.
vp.add(table);
RootPanel.get().add(vp);