Related
I have got implemented filtering in nattables. How to add sorting? do I have to create new columnHeaderLayer? Here is my code:
1 class BodyLayerStack
class BodyLayerStack extends AbstractLayerTransform {
private final FilterList<TableLine> filterList;
private final SelectionLayer selectionLayer;
private final DataLayer bodyDataLayer;
private final IRowDataProvider<TableLine> bodyDataProvider;
private final SortedList<TableLine> sortedList;
public DataLayer getBodyDataLayer() {
return bodyDataLayer;
}
public SelectionLayer getSelectionLayer() {
return selectionLayer;
}
public IRowDataProvider<TableLine> getBodyDataProvider() {
return bodyDataProvider;
}
getting values from tables:
public BodyLayerStack(List<TableLine> values, IColumnAccessor<TableLine> columnAccessor, Integer[] columnIndicesForRowHeaders) {
EventList<TableLine> eventList = GlazedLists.eventList(values);
TransformedList<TableLine, TableLine> rowObjectsGlazedList = GlazedLists.threadSafeList(eventList);
creating filter and sorted list:
this.filterList = new FilterList<>(rowObjectsGlazedList);
this.sortedList = new SortedList<>(filterList, null);
this.bodyDataProvider = new ListDataProvider<TableLine>(this.sortedList, columnAccessor);
bodyDataLayer = new DataLayer(this.bodyDataProvider);
ColumnOverrideLabelAccumulator bodyLabelAccumulator = new ColumnOverrideLabelAccumulator(bodyDataLayer);
bodyDataLayer.setConfigLabelAccumulator(bodyLabelAccumulator);
if( columnIndicesForRowHeaders != null ) {
for ( int i = 0; i < columnIndicesForRowHeaders.length; i++ ) {
bodyLabelAccumulator.registerColumnOverrides(
columnIndicesForRowHeaders[i],
RowHeaderLabel);
}
}
event layer:
GlazedListsEventLayer<TableLine> glazedListsEventLayer =
new GlazedListsEventLayer<>(bodyDataLayer, this.filterList);
this.selectionLayer = new SelectionLayer(glazedListsEventLayer, false);
selectionLayer.setSelectionModel(new RowSelectionModel<TableLine>(selectionLayer, this.bodyDataProvider, new IRowIdAccessor<TableLine>()
{
#Override
public Serializable getRowId(TableLine line)
{
return line.getId();
}
}));
adding configuration to selection layer
selectionLayer.addConfiguration(new DefaultSelectionLayerConfiguration()
{
#Override
protected void addSelectionUIBindings()
{
addConfiguration(new SelectionBindings());
}
#SuppressWarnings("rawtypes")
#Override
protected void addMoveSelectionConfig()
{
addConfiguration(new RowOnlySelectionConfiguration());
}
});
viewport layer + return
ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
setUnderlyingLayer(viewportLayer);
}
public FilterList<TableLine> getFilterList() {
return this.filterList;
}
}
If you want to add sorting by click on column header cells, you have to add the SortHeaderLayer to your column header layer stack.
Please check the NatTable examples, like for example the SortableFilterableColumnGroupExample
I have been working on AutoCompleteTextView. I was able to get the suggestion and all in the drop down list as we type.
My question is: Can we highlight the typed character in the suggestion drop down list?
I have achieved the functionality. The solution is as follows:
AutoCompleteAdapter.java
public class AutoCompleteAdapter extends ArrayAdapter<String> implements
Filterable {
private ArrayList<String> fullList;
private ArrayList<String> mOriginalValues;
private ArrayFilter mFilter;
LayoutInflater inflater;
String text = "";
public AutoCompleteAdapter(Context context, int resource,
int textViewResourceId, List<String> objects) {
super(context, resource, textViewResourceId, objects);
fullList = (ArrayList<String>) objects;
mOriginalValues = new ArrayList<String>(fullList);
inflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return fullList.size();
}
#Override
public String getItem(int position) {
return fullList.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
// tvViewResourceId = (TextView) view.findViewById(android.R.id.text1);
String item = getItem(position);
Log.d("item", "" + item);
if (convertView == null) {
convertView = view = inflater.inflate(
android.R.layout.simple_dropdown_item_1line, null);
}
// Lookup view for data population
TextView myTv = (TextView) convertView.findViewById(android.R.id.text1);
myTv.setText(highlight(text, item));
return view;
}
#Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
return mFilter;
}
private class ArrayFilter extends Filter {
private Object lock;
#Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (prefix != null) {
text = prefix.toString();
}
if (mOriginalValues == null) {
synchronized (lock) {
mOriginalValues = new ArrayList<String>(fullList);
}
}
if (prefix == null || prefix.length() == 0) {
synchronized (lock) {
ArrayList<String> list = new ArrayList<String>(
mOriginalValues);
results.values = list;
results.count = list.size();
}
} else {
final String prefixString = prefix.toString().toLowerCase();
ArrayList<String> values = mOriginalValues;
int count = values.size();
ArrayList<String> newValues = new ArrayList<String>(count);
for (int i = 0; i < count; i++) {
String item = values.get(i);
if (item.toLowerCase().contains(prefixString)) {
newValues.add(item);
}
}
results.values = newValues;
results.count = newValues.size();
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
if (results.values != null) {
fullList = (ArrayList<String>) results.values;
} else {
fullList = new ArrayList<String>();
}
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
public static CharSequence highlight(String search, String originalText) {
// ignore case and accents
// the same thing should have been done for the search text
String normalizedText = Normalizer
.normalize(originalText, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "")
.toLowerCase(Locale.ENGLISH);
int start = normalizedText.indexOf(search.toLowerCase(Locale.ENGLISH));
if (start < 0) {
// not found, nothing to to
return originalText;
} else {
// highlight each appearance in the original text
// while searching in normalized text
Spannable highlighted = new SpannableString(originalText);
while (start >= 0) {
int spanStart = Math.min(start, originalText.length());
int spanEnd = Math.min(start + search.length(),
originalText.length());
highlighted.setSpan(new ForegroundColorSpan(Color.BLUE),
spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
start = normalizedText.indexOf(search, spanEnd);
}
return highlighted;
}
}
}
MainActivity.java
public class MainActivity extends Activity {
String[] languages = { "C", "C++", "Java", "C#", "PHP", "JavaScript",
"jQuery", "AJAX", "JSON" };
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
List<String> wordList = new ArrayList<String>();
Collections.addAll(wordList, languages);
AutoCompleteAdapter adapter = new AutoCompleteAdapter(this,
android.R.layout.simple_dropdown_item_1line,
android.R.id.text1,wordList);
AutoCompleteTextView acTextView = (AutoCompleteTextView) findViewById(R.id.languages);
acTextView.setThreshold(1);
acTextView.setAdapter(adapter);
}
}
Working like charm!
Enjoy!
I reckon that should be possible, provided you know the index/indices of the character(s) the user typed last. You can then use a SpannableStringBuilder and set a ForegroundColorSpan and BackgroundColorSpan to give the character(s) the appearance of a highlight.
The idea looks somewhat like this:
// start & end of the highlight
int start = ...;
int end = ...;
SpannableStringBuilder builder = new SpannableStringBuilder(suggestionText);
// set foreground color (text color) - optional, you may not want to change the text color too
builder.setSpan(new ForegroundColorSpan(Color.RED), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// set background color
builder.setSpan(new BackgroundColorSpan(Color.YELLOW), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// set result to AutoCompleteTextView
autocompleteTextview.setText(builder);
Note that the 'highlight' will remain as long as you don't type another character. You may want to remove the highlight when e.g. the user changes the cursor position in the AutoCompleteTextView, but I'll leave that up to you.
I know it's to late for answering this question , But as I personally battled to find the answer , finally I wrote it myself (with the help of the answer from #MH. ofcourse), so here it is :
First , You have to create a Custom ArrayAdapter :
public class AdapterAustocomplete extends ArrayAdapter<String> {
private static final String TAG = "AdapterAustocomplete";
String q = "";
public AdapterAustocomplete(Context context, int resource, List objects) {
super(context, resource, objects);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
String item = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView =
// I'll use a custom view for each Item , this way I can customize it also!
G.inflater.from(getContext()).inflate(R.layout.textview_autocomplete, parent, false);
}
// Lookup view for data population
TextView myTv = (TextView) convertView.findViewById(R.id.txt_autocomplete);
int start = item.indexOf(q);
int end = q.length()+start;
SpannableStringBuilder builder = new SpannableStringBuilder(item);
builder.setSpan(new ForegroundColorSpan(Color.RED), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
myTv.setText(builder);
return convertView;
}
public void setQ(String q) {
this.q = q;
}
}
And in the Code that you want to set the adapter for AutoCompleteTextView ;
AutoCompleteTextView myAutoComplete = findViewById(its_id);
AdapterAustocomplete adapter_autoComplete = new AdapterAustocomplete(getActivity(), 0, items); // items is an arrayList of Strings
adapter_autoComplete.setQ(q);
myAutoComplete.setAdapter(adapter_autoComplete);
Thanks to vadher jitendra I wrote the same and fixed some bugs.
Changed a dropdown layout to own.
Added showing a full list when clicking inside AutoCompleteTextView.
Fixed a bug of freezing the list when showing (added a check for empty string in highlight).
row_dropdown.xml (item layout):
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/text1"
style="?android:attr/dropDownItemStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:ellipsize="marquee"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:singleLine="true"
android:textColor="#333333"
android:textSize="15sp"
tools:text="text"
tools:textAppearance="?android:attr/textAppearanceLargePopupMenu" />
To filter a list when typing we should implement ArrayAdapter. It depends on items (T class). You can later use AutoCompleteAdapter<String> or any data class you like.
AutoCompleteAdapter:
import android.content.Context;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class AutoCompleteAdapter<T> extends ArrayAdapter<T> implements Filterable {
private Context context;
#LayoutRes
private int layoutRes;
#IdRes
private int textViewResId;
private ArrayList<T> fullList;
private ArrayList<T> originalValues;
private ArrayFilter filter;
private LayoutInflater inflater;
private String query = "";
public AutoCompleteAdapter(#NonNull Context context, #LayoutRes int resource, #IdRes int textViewResourceId, #NonNull List<T> objects) {
super(context, resource, textViewResourceId, objects);
this.context = context;
layoutRes = resource;
textViewResId = textViewResourceId;
fullList = (ArrayList<T>) objects;
originalValues = new ArrayList<>(fullList);
inflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return fullList.size();
}
#Override
public T getItem(int position) {
return fullList.get(position);
}
/**
* You can use either
* vadher jitendra method (getView)
* or get the method from ArrayAdapter.java.
*/
// #NotNull
// #Override
// public View getView(int position, View convertView, ViewGroup parent) {
// View view = convertView;
// T item = getItem(position);
// Log.d("item", "" + item);
// if (convertView == null) {
// convertView = view = inflater.inflate(layoutRes, null);
// }
// // Lookup view for data population
// TextView myTv = convertView.findViewById(textViewResId);
// myTv.setText(highlight(query, item));
// return view;
// }
#Override
public #NonNull
View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
return createViewFromResource(inflater, position, convertView, parent, layoutRes);
}
private #NonNull
View createViewFromResource(#NonNull LayoutInflater inflater, int position,
#Nullable View convertView, #NonNull ViewGroup parent, int resource) {
final View view;
final TextView text;
if (convertView == null) {
view = inflater.inflate(resource, parent, false);
} else {
view = convertView;
}
try {
if (textViewResId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} else {
// Otherwise, find the TextView field within the layout
text = view.findViewById(textViewResId);
if (text == null) {
throw new RuntimeException("Failed to find view with ID "
+ context.getResources().getResourceName(textViewResId)
+ " in item layout");
}
}
} catch (ClassCastException e) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
throw new IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e);
}
final T item = getItem(position);
text.setText(highlight(query, item.toString()));
// if (item instanceof CharSequence) {
// text.setText(highlight(query, (CharSequence) item));
// } else {
// text.setText(item.toString());
// }
return view;
}
#Override
public #NonNull
Filter getFilter() {
if (filter == null) {
filter = new ArrayFilter();
}
return filter;
}
private class ArrayFilter extends Filter {
private final Object lock = new Object();
#Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (prefix == null) {
query = "";
} else {
query = prefix.toString();
}
if (originalValues == null) {
synchronized (lock) {
originalValues = new ArrayList<>(fullList);
}
}
if (prefix == null || prefix.length() == 0) {
synchronized (lock) {
ArrayList<T> list = new ArrayList<>(originalValues);
results.values = list;
results.count = list.size();
}
} else {
final String prefixString = prefix.toString().toLowerCase();
ArrayList<T> values = originalValues;
int count = values.size();
ArrayList<T> newValues = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
T item = values.get(i);
if (item.toString().toLowerCase().contains(prefixString)) {
newValues.add(item);
}
}
results.values = newValues;
results.count = newValues.size();
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.values != null) {
fullList = (ArrayList<T>) results.values;
} else {
fullList = new ArrayList<>();
}
if (results.count > 0) {
notifyDataSetChanged();
} else {
notifyDataSetInvalidated();
}
}
}
private static CharSequence highlight(#NonNull String search, #NonNull CharSequence originalText) {
if (search.isEmpty())
return originalText;
// ignore case and accents
// the same thing should have been done for the search text
String normalizedText = Normalizer
.normalize(originalText, Normalizer.Form.NFD)
.replaceAll("\\p{InCombiningDiacriticalMarks}+", "")
.toLowerCase(Locale.ENGLISH);
int start = normalizedText.indexOf(search.toLowerCase(Locale.ENGLISH));
if (start < 0) {
// not found, nothing to do
return originalText;
} else {
// highlight each appearance in the original text
// while searching in normalized text
Spannable highlighted = new SpannableString(originalText);
while (start >= 0) {
int spanStart = Math.min(start, originalText.length());
int spanEnd = Math.min(start + search.length(),
originalText.length());
highlighted.setSpan(new StyleSpan(Typeface.BOLD), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
start = normalizedText.indexOf(search, spanEnd);
}
return highlighted;
}
}
}
In order to show dropdown list when clicked inside AutoCompleteTextView we need to override setOnTouchListener as described in https://stackoverflow.com/a/26036902/2914140. Lint also prints warnings, so we have to write a custom view:
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.appcompat.widget.AppCompatAutoCompleteTextView;
/*
Avoids a warning "Custom view `AutoCompleteTextView` has setOnTouchListener called on it but does not override performClick".
*/
public class AutoCompleteTV extends AppCompatAutoCompleteTextView {
public AutoCompleteTV(Context context) {
super(context);
}
public AutoCompleteTV(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AutoCompleteTV(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
performClick();
}
return super.onTouchEvent(event);
}
#Override
public boolean performClick() {
super.performClick();
return true;
}
}
Then use it in activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.autocompletetextview1.AutoCompleteTV
android:id="#+id/languages"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:completionThreshold="1"
android:hint="language"
android:imeOptions="actionNext"
android:maxLines="1"
android:paddingLeft="10dp"
android:paddingTop="15dp"
android:paddingRight="10dp"
android:paddingBottom="15dp"
android:singleLine="true"
android:textColor="#333333"
android:textColorHint="#808080"
android:textSize="12sp" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
I use TextInputLayout here for better decoration, in this case we have to add Material Design Components:
in build.gradle:
implementation 'com.google.android.material:material:1.3.0-alpha01'
and in styles.xml:
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
...
MainActivity:
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.AutoCompleteTextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
String[] items = {"C", "C++", "Java", "C#", "PHP", "JavaScript", "jQuery", "AJAX", "JSON"};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<DataClass> wordList = new ArrayList<>();
for (int i = 0; i < items.length; i++) {
DataClass data = new DataClass(i, items[i]);
wordList.add(data);
}
AutoCompleteAdapter<DataClass> adapter = new AutoCompleteAdapter<>(this,
R.layout.row_dropdown, R.id.text1, wordList);
//adapter.setDropDownViewResource(R.layout.row_dropdown);
AutoCompleteTV acTextView = findViewById(R.id.languages);
acTextView.setThreshold(1);
acTextView.setAdapter(adapter);
acTextView.setText("Java");
acTextView.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
((AutoCompleteTextView) v).showDropDown();
v.requestFocus();
v.performClick(); // Added to avoid warning "onTouch lambda should call View#performClick when a click is detected".
}
return false;
}
);
}
}
Using GWT 2.4...
I am building upon a complex Composite dual view/edit mode implementation that is backed GWT's DataGrid and MultiSelectionModel. My goal is for a user to be able to click a checkbox in each row that they'd like to post updates for.
Here's a screenshot from a semi-functional interface:
Note the selected (highlighted) rows.
Now the problem is that when I type something in any of the cells (e.g., the first row's $ cell under the $/Mw 1 composite cell header), then click that row's checkbox (or any other row's checkbox for that matter) to select or de-select, the value gets reset to the original value when the screen's data was first requested. Not desired behavior by any stretch!
Let's take a look at my custom implementation for the grid. (Excuse the length).
public abstract class ToggleableGrid<T extends Identifiable<?>> extends Composite {
private static final int CHKBOX_COLUMN_WIDTH = App.INSTANCE.checkboxColumnWidth();
private static final DisplayMode DEFAULT_MODE = DisplayMode.VIEW;
private ProvidesKey<T> keyProvider;
private DataGrid<T> grid;
private MultiSelectionModel<T> selectionModel;
private ListDataProvider<T> dataProvider;
private int tabIndex = 0;
public ToggleableGrid() {
final DataGridConfiguration config = new DefaultDataGridConfiguration();
initGrid(config);
}
public ToggleableGrid(DataGridConfiguration config) {
initGrid(config);
}
private void initGrid(DataGridConfiguration config) {
keyProvider = new ProvidesKey<T>() {
#Override
public Object getKey(T item) {
return item == null ? null : item.getId();
}
};
grid = new DataGrid<T>(config.getPageSize(), config.getResources(), keyProvider);
// Set the message to display when the table is empty.
grid.setEmptyTableWidget(new Label(UiMessages.INSTANCE.no_results()));
initWidget(grid);
setVisible(true);
}
public void setInput(List<T> content) {
setInput(content, DEFAULT_MODE);
}
public void setInput(List<T> content, DisplayMode mode) {
resetTableColumns();
if (isInEditMode(mode)) {
// Add a selection model so we can select cells
selectionModel = new MultiSelectionModel<T>(keyProvider);
grid.setSelectionModel(selectionModel, DefaultSelectionEventManager.<T> createCheckboxManager(0));
addRowSelector();
}
dataProvider = new ListDataProvider<T>(content);
final ListHandler<T> sortHandler = new ListHandler<T>(dataProvider.getList());
grid.addColumnSortHandler(sortHandler);
initializeStructure(constructMetadata(), sortHandler, mode);
dataProvider.addDataDisplay(grid);
}
// see https://stackoverflow.com/questions/3772480/remove-all-columns-from-a-celltable
// concrete classes are forced to maintain a handle on all columns added
private void resetTableColumns() {
for (final Column<T, ?> column: allColumns()) {
grid.removeColumn(column);
}
allColumns().clear();
}
protected boolean isInEditMode(DisplayMode currentDisplayMode) {
boolean result = false;
if (currentDisplayMode.equals(DisplayMode.EDIT)) {
result = true;
}
return result;
}
protected abstract Set<Column<T, ?>> allColumns();
protected abstract TableMetadata constructMetadata();
protected abstract void initializeStructure(TableMetadata metadata, ListHandler<T> sortHandler, DisplayMode mode);
protected void setColumnHorizontalAlignment(Column<T, ?> column, HorizontalAlignmentConstant alignment) {
column.setHorizontalAlignment(alignment);
}
// TODO figure out how to add a checkbox to column header that provides select/de-select all capability
// see https://stackoverflow.com/questions/6174689/gwt-celltable-programmatically-select-checkboxcell
protected void addRowSelector() {
final Column<T, Boolean> rowSelectColumn = new Column<T, Boolean>(new CheckboxCell(true, false)) {
#Override
public Boolean getValue(T value) {
Boolean result;
// check for null value and return null;
if(value == null || value.getId() == null) {
result = null;
} else { // get value from the selection model
result = selectionModel.isSelected(value);
}
return result;
}
};
addColumn(rowSelectColumn, UiMessages.INSTANCE.select());
setColumnWidth(rowSelectColumn, CHKBOX_COLUMN_WIDTH, Unit.PX);
setColumnHorizontalAlignment(rowSelectColumn, HasHorizontalAlignment.ALIGN_CENTER);
}
protected void setColumnWidth(Column<T, ?> column, int width, Unit unit) {
grid.setColumnWidth(column, width, unit);
}
protected void addColumn(Column<T, ?> column, String columnHeaderName) {
addColumn(column, columnHeaderName, HasHorizontalAlignment.ALIGN_RIGHT);
}
protected void addColumn(Column<T, ?> column, String columnHeaderName, HorizontalAlignmentConstant alignment) {
final SafeHtmlBuilder sb = new SafeHtmlBuilder();
final String divStart = "<div align=\""+ alignment.getTextAlignString() + "\" class=\"" +UiResources.INSTANCE.style().word_wrap() + "\">";
sb.appendHtmlConstant(divStart).appendEscaped(columnHeaderName).appendHtmlConstant("</div>");
final SafeHtml header = sb.toSafeHtml();
grid.addColumn(column, header);
allColumns().add(column);
}
protected CompositeCell<T> generateCompositeCell(final List<HasCell<T, ?>> hasCells) {
final CompositeCell<T> compositeCell = new CompositeCell<T>(hasCells) {
#Override
public void render(Context context, T value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<table><tbody><tr>");
super.render(context, value, sb);
sb.appendHtmlConstant("</tr></tbody></table>");
}
#Override
protected Element getContainerElement(Element parent) {
// Return the first TR element in the table.
return parent.getFirstChildElement().getFirstChildElement().getFirstChildElement();
}
#Override
protected <X> void render(Context context, T value,
SafeHtmlBuilder sb, HasCell<T, X> hasCell) {
final Cell<X> cell = hasCell.getCell();
sb.appendHtmlConstant("<td>");
cell.render(context, hasCell.getValue(value), sb);
sb.appendHtmlConstant("</td>");
}
};
return compositeCell;
}
// FIXME not working quite the way we'd expect, index incremented within column for each row, not each row by column
protected int nextTabIndex() {
tabIndex++;
return tabIndex;
}
protected AbstractCellTable<T> getGrid() {
return grid;
}
/**
* Gets the selected (row(s) of) data from grid (used in edit mode)
* #return the selected data (as per selection model)
*/
public List<T> getSelectedData() {
final List<T> data = new ArrayList<T>();
data.addAll(selectionModel.getSelectedSet());
return data;
}
/**
* Gets all (row(s) of) data in grid (used in edit mode)
* #return all data as list
*/
public List<T> getAllData() {
return dataProvider.getList();
}
/**
* Clears the currently selected (row(s) of) data (used in edit mode)
*/
public void clearSelectedData() {
selectionModel.clear();
grid.redraw();
}
}
So, the interesting methods to stare at above (I think) are setInput, generateCompositeCell and addRowSelector.
We initialize the grid with List data and set a display mode in setInput. It's here as well that the selection model is initialized. It uses GWT's DefaultSelectionEventManager createCheckboxManager().
I've been trying to grok the event model, but it eludes me. I've visited the following sources online, but have come up short on avenues to solving this problem.
-- https://groups.google.com/forum/?fromgroups#!topic/google-web-toolkit/k5sfURxDaVg
AbstractInputCell's getConsumedEventsImpl adds focus, blur and keydown, so this (I believe) is not a track I need to explore
-- GWT CellTable programmatically select CheckBoxCell
The various ways you can instantiate a CheckBoxCell got me curious, and I've tried many constructor argument permutations, but the one I settled on (true, false) is (I believe) the right one
Agreeing here and now (before being reprimanded) that there's perhaps some unnecessary complexity in my implementation, but I am looking for guidance nonetheless. Thanks!
Update
If it helps here's an impl of the aforementioned ToggleableGrid. If anything it gives you more detail on what goes into each CompositeCell. For details on AbstractValidatableColumn and ValidatableInputCell, see: In search of a GWT validation example... where art thou?.
public class EnergyOfferGrid extends ToggleableGrid<EnergyOfferDTO> {
public EnergyOfferGrid() {
super();
}
public EnergyOfferGrid(DataGridConfiguration config) {
super(config);
}
private static final int MAX_NUMBER_OF_MW_PRICE_POINTS = App.INSTANCE.maxNoOfMwPricePoints();
private Set<Column<EnergyOfferDTO, ?>> columns = new HashSet<Column<EnergyOfferDTO, ?>>();
#Override
protected Set<Column<EnergyOfferDTO, ?>> allColumns() {
return columns;
}
#Override
protected TableMetadata constructMetadata() {
final TableMetadata metadata = new TableMetadata();
// TODO Consider a predefined set of ReferenceData to be held in a common package
// Use Slope
metadata.addColumnMetadata(UiMessages.INSTANCE.use_slope(), new String[] {UiMessages.INSTANCE.yes(), UiMessages.INSTANCE.no()}, new String[] {"true", "false"});
return metadata;
}
#Override
protected void initializeStructure(TableMetadata metadata, ListHandler<EnergyOfferDTO> sortHandler, DisplayMode currentDisplayMode) {
addHourColumn(sortHandler);
addUseSlopeColumn(metadata, sortHandler, currentDisplayMode);
for (int i = 0; i < MAX_NUMBER_OF_MW_PRICE_POINTS; i++) { // zero-based indexing
addPriceMwColumn(i, currentDisplayMode);
}
}
protected void addHourColumn(ListHandler<EnergyOfferDTO> sortHandler) {
final Column<EnergyOfferDTO, String> hourColumn = new Column<EnergyOfferDTO, String>(new TextCell()) {
#Override
public String getValue(EnergyOfferDTO energyOffer) {
String result = "";
if (energyOffer.getId() != null) {
final String isoDateTime = energyOffer.getId().getOperatingHour();
if (isoDateTime != null && !isoDateTime.isEmpty()) {
final Date dateTime = CSTimeUtil.isoToDate(isoDateTime);
if (dateTime != null) {
result = CSTimeUtil.dateToHour(dateTime);
}
}
}
return result;
}
};
hourColumn.setSortable(true);
sortHandler.setComparator(hourColumn, new Comparator<EnergyOfferDTO>() {
#Override
public int compare(EnergyOfferDTO eo1, EnergyOfferDTO eo2) {
final String date1 = eo1.getId() != null ? eo1.getId().getOperatingHour() : "";
final String date2 = eo2.getId() != null ? eo2.getId().getOperatingHour() : "";
return date1.compareTo(date2);
}
});
// We know that the data is sorted by hour by default.
getGrid(). getColumnSortList().push(hourColumn);
addColumn(hourColumn, UiMessages.INSTANCE.hour());
setColumnWidth(hourColumn, 45, Unit.PX);
setColumnHorizontalAlignment(hourColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}
protected void addUseSlopeColumn(TableMetadata metadata, ListHandler<EnergyOfferDTO> sortHandler, DisplayMode currentDisplayMode) {
final ReferenceData refData = metadata.allColumnMetadata().get(UiMessages.INSTANCE.use_slope());
Column<EnergyOfferDTO, String> useSlopeColumn;
Cell<String> cell;
if (isInEditMode(currentDisplayMode)) {
cell = new ReferenceDataBackedSelectionCell(refData);
} else {
cell = new TextCell();
}
useSlopeColumn = new Column<EnergyOfferDTO, String>(cell) {
#Override
public String getValue(EnergyOfferDTO energyOffer) {
return refData.getDisplayValueForSubmitValue(Boolean.toString(energyOffer.isSlopeUsed()));
}
};
useSlopeColumn.setSortable(true);
sortHandler.setComparator(useSlopeColumn, new Comparator<EnergyOfferDTO>() {
#Override
public int compare(EnergyOfferDTO eo1, EnergyOfferDTO eo2) {
final String slopeUsed1 = String.valueOf(eo1.isSlopeUsed());
final String slopeUsed2 = String.valueOf(eo1.isSlopeUsed());
return slopeUsed1.compareTo(slopeUsed2);
}
});
addColumn(useSlopeColumn, UiMessages.INSTANCE.use_slope());
setColumnWidth(useSlopeColumn, 75, Unit.PX);
setColumnHorizontalAlignment(useSlopeColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}
protected void addPriceMwColumn(final int colIndex, DisplayMode currentDisplayMode) {
// Construct a composite cell for energy offers that includes a pair of text inputs
final List<HasCell<EnergyOfferDTO, ?>> columns = new ArrayList<
HasCell<EnergyOfferDTO, ?>>();
// this DTO is passed along so that price and mw values for new entries are kept together
final OfferPriceMwPair newOfferPriceMwPair = new OfferPriceMwPair();
// Price
final Column<EnergyOfferDTO, String> priceColumn = generatePriceColumn(colIndex, newOfferPriceMwPair, currentDisplayMode);
columns.add(priceColumn);
// MW
final Column<EnergyOfferDTO, String> mwColumn = generateMwColumn(colIndex, newOfferPriceMwPair, currentDisplayMode);
columns.add(mwColumn);
// Composite
final CompositeCell<EnergyOfferDTO> priceMwColumnInnards = generateCompositeCell(columns);
final IdentityColumn<EnergyOfferDTO> priceMwColumn = new IdentityColumn<EnergyOfferDTO>(priceMwColumnInnards);
final StringBuilder colHeader = new StringBuilder();
colHeader.append(UiMessages.INSTANCE.price_mw_header()).append(" ").append(String.valueOf(colIndex + 1));
addColumn(priceMwColumn, colHeader.toString());
setColumnWidth(priceMwColumn, 7, Unit.EM);
setColumnHorizontalAlignment(priceMwColumn, HasHorizontalAlignment.ALIGN_RIGHT);
}
protected Column<EnergyOfferDTO, String> generatePriceColumn(final int colIndex, final OfferPriceMwPair newOfferPriceMwPair, DisplayMode currentDisplayMode) {
Column<EnergyOfferDTO, String> priceColumn;
if (isInEditMode(currentDisplayMode)) {
priceColumn = new BigDecimalValidatableColumn<EnergyOfferDTO, OfferPriceMwPair>(nextTabIndex(), getGrid()) {
#Override
public String getValue(EnergyOfferDTO energyOffer) {
return obtainPriceValue(colIndex, energyOffer, false);
}
#Override
public void doUpdate(int index, EnergyOfferDTO energyOffer, String value) {
if (value != null && !value.isEmpty()) {
// number format exceptions should be caught and handled by event bus's handle method
final double valueAsDouble = NumberFormat.getDecimalFormat().parse(value);
final BigDecimal price = BigDecimal.valueOf(valueAsDouble);
final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
final OfferPriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
if (offerPriceMwPair == null) { // we have a new price value
newOfferPriceMwPair.setPrice(price);
offerPriceCurve.add(newOfferPriceMwPair);
} else {
offerPriceMwPair.setPrice(price);
}
}
}
#Override
protected String getPropertyName() {
return "price";
}
#Override
protected Class<OfferPriceMwPair> getPropertyOwner() {
return OfferPriceMwPair.class;
}
};
} else {
priceColumn = new Column<EnergyOfferDTO, String>(new TextCell()) {
#Override
public String getValue(EnergyOfferDTO energyOffer) {
final String result = obtainPriceValue(colIndex, energyOffer, true);
return result;
}
};
}
return priceColumn;
}
private String obtainPriceValue(final int colIndex, EnergyOfferDTO energyOffer, boolean withCurrency) {
String result = "";
if (energyOffer != null) {
final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
final int numberOfPairs = offerPriceCurve.size();
if (colIndex < numberOfPairs) {
final OfferPriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
if (offerPriceMwPair != null) {
final BigDecimal price = offerPriceMwPair.getPrice();
if (price != null) {
final double value = price.doubleValue();
if (withCurrency) {
result = NumberFormat.getCurrencyFormat().format(value);
} else {
result = NumberFormat.getDecimalFormat().format(value);
}
}
}
}
}
return result;
}
protected Column<EnergyOfferDTO, String> generateMwColumn(final int colIndex, final OfferPriceMwPair newOfferPriceMwPair, DisplayMode currentDisplayMode) {
Column<EnergyOfferDTO, String> mwColumn;
if (isInEditMode(currentDisplayMode)) {
mwColumn = new BigDecimalValidatableColumn<EnergyOfferDTO, PriceMwPair>(nextTabIndex(), getGrid()) {
#Override
public String getValue(EnergyOfferDTO energyOffer) {
return obtainMwValue(colIndex, energyOffer);
}
#Override
public void doUpdate(int index, EnergyOfferDTO energyOffer, String value) {
if (value != null && !value.isEmpty()) {
// number format exceptions should be caught and handled by event bus's handle method
final double valueAsDouble = NumberFormat.getDecimalFormat().parse(value);
final BigDecimal mw = BigDecimal.valueOf(valueAsDouble);
final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
final OfferPriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
if (offerPriceMwPair == null) { // we have a new price value
newOfferPriceMwPair.setMw(mw);
offerPriceCurve.add(newOfferPriceMwPair);
} else {
offerPriceMwPair.setMw(mw);
}
}
}
#Override
protected String getPropertyName() {
return "mw";
}
#Override
protected Class<PriceMwPair> getPropertyOwner() {
return PriceMwPair.class;
}
};
} else {
mwColumn = new Column<EnergyOfferDTO, String>(new TextCell()) {
#Override
public String getValue(EnergyOfferDTO energyOffer) {
final String result = obtainMwValue(colIndex, energyOffer);
return result;
}
};
}
return mwColumn;
}
private String obtainMwValue(final int colIndex, EnergyOfferDTO energyOffer) {
String result = "";
if (energyOffer != null) {
final List<OfferPriceMwPair> offerPriceCurve = energyOffer.getCurve();
final int numberOfPairs = offerPriceCurve.size();
if (colIndex < numberOfPairs) {
final PriceMwPair offerPriceMwPair = offerPriceCurve.get(colIndex);
if (offerPriceMwPair != null) {
final BigDecimal mw = offerPriceMwPair.getMw();
if (mw != null) {
result = NumberFormat.getDecimalFormat().format(mw);
}
}
}
}
return result;
}
}
All that custom work w.r.t. WrapperCell and CompositeValidatableColumn was unnecessary.
It turns out that there's a way you should not construct CompositeCells. See http://code.google.com/p/google-web-toolkit/issues/detail?id=5714. My CompositeCells were not receiving events. So, I changed the way I construct them in ToggleableGrid.
protected CompositeCell<T> generateCompositeCell(final List<HasCell<T, String>> hasCells) {
final CompositeCell<T> compositeCell = new CompositeCell<T>(hasCells) {
// to not run afoul of http://code.google.com/p/google-web-toolkit/issues/detail?id=5714
#Override
public void render(Context context, T value, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<div style=\"display: inline\">");
super.render(context, value, sb);
sb.appendHtmlConstant("</div>");
}
#Override
protected Element getContainerElement(Element parent) {
// Return the first element in the DIV.
return parent.getFirstChildElement();
}
};
return compositeCell;
}
After that change and incorporating my other validation-oriented classes: ValidatableFieldUpdater, AbstractValidatableColumn (and derivatives), ValidatableInputField and ConversionResult, life couldn't be more grand!
Here I have an complete and very easy example to dynamically add/remove
nodes to an celltree. My example is not working very well. It seems there
is an refresh problem. Only closing/expanding the nodes will show the correct
result. I also did not found any answer in this forum which fits to this problem.
Maybe somebody can try my example and tell me where the problem is.
Any other hint is also very appreciated.
Greetings, Marco
import java.util.ArrayList;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.CellTree;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;
public class MyCelltreeTest implements EntryPoint {
private AbsolutePanel absolutePanel;
private CellTree cellTree;
private Button btnAdd;
private Button btnRemove;
private MyTreeModel myTreeModel;
private SingleSelectionModel<MyNode> selectionModelCellTree = null;
#Override
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
rootPanel.add(getAbsolutePanel(), 0, 0);
}
private AbsolutePanel getAbsolutePanel() {
if (absolutePanel == null) {
absolutePanel = new AbsolutePanel();
absolutePanel.setSize("612px", "482px");
absolutePanel.add(getCellTree(), 0, 0);
absolutePanel.add(getBtnAdd(), 265, 428);
absolutePanel.add(getBtnRemove(), 336, 428);
}
return absolutePanel;
}
private CellTree getCellTree() {
if (cellTree == null) {
myTreeModel = new MyTreeModel();
cellTree = new CellTree(myTreeModel, null);
cellTree.setSize("285px", "401px");
}
return cellTree;
}
private Button getBtnAdd() {
if (btnAdd == null) {
btnAdd = new Button("Add");
btnAdd.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
if(node != null)
myTreeModel.addNew(node, "Bla");
}
});
}
return btnAdd;
}
private Button getBtnRemove() {
if (btnRemove == null) {
btnRemove = new Button("Remove");
btnRemove.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
if(node != null)
myTreeModel.remove(node);
}
});
}
return btnRemove;
}
public class MyNode {
private String name;
private ArrayList<MyNode> childs; //nodes childrens
private MyNode parent; //track internal parent
private MyCell cell; //for refresh - reference to visual component
public MyNode(String name) {
super();
parent = null;
this.name = name;
childs = new ArrayList<MyNode>();
}
public void addSubMenu(MyNode m) {
m.parent = this;
childs.add(m);
}
public void removeMenu(MyNode m) {
m.getParent().childs.remove(m);
}
public boolean hasChildrens() {
return childs.size()>0;
}
public ArrayList<MyNode> getList() {
return childs;
}
public MyNode getParent() {
return parent;
}
public void setCell(MyCell cell) {
this.cell = cell;
}
public void refresh() {
if(parent!=null) {
parent.refresh();
}
if (cell!=null) {
cell.refresh(); //refresh tree
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class MyTreeModel implements TreeViewModel {
private MyNode officialRoot; //default not dynamic
private MyNode studentRoot; //default not dynamic
private MyNode testRoot; //default not dynamic
private MyNode root;
public MyNode getRoot() { // to set CellTree root
return root;
}
public MyTreeModel() {
selectionModelCellTree = new SingleSelectionModel<MyNode>();
root = new MyNode("root");
// Default items
officialRoot = new MyNode("Cat"); //some basic static data
studentRoot = new MyNode("Dog");
testRoot = new MyNode("Fish");
root.addSubMenu(officialRoot);
root.addSubMenu(studentRoot);
root.addSubMenu(testRoot);
}
//example of add add logic
public void addNew(MyNode myparent, String name) {
myparent.addSubMenu(new MyNode(name));
myparent.refresh(); //HERE refresh tree
}
public void remove(MyNode objToRemove) {
objToRemove.removeMenu(objToRemove);
objToRemove.refresh();
}
#Override
public <T> NodeInfo<?> getNodeInfo(T value) {
ListDataProvider<MyNode> dataProvider;
MyNode myValue = null;
if (value == null) { // root is not set
dataProvider = new ListDataProvider<MyNode>(root.getList());
} else {
myValue = (MyNode) value;
dataProvider = new ListDataProvider<MyNode>(myValue.getList());
}
MyCell cell = new MyCell(dataProvider); //HERE Add reference
if (myValue != null)
myValue.setCell(cell);
return new DefaultNodeInfo<MyNode>(dataProvider, cell, selectionModelCellTree, null);
}
#Override
public boolean isLeaf(Object value) {
if (value instanceof MyNode) {
MyNode t = (MyNode) value;
if (!t.hasChildrens())
return true;
return false;
}
return false;
}
}
public class MyCell extends AbstractCell<MyNode> {
ListDataProvider<MyNode> dataProvider; //for refresh
public MyCell(ListDataProvider<MyNode> dataProvider) {
super();
this.dataProvider = dataProvider;
}
public void refresh() {
dataProvider.refresh();
}
#Override
public void render(Context context, MyNode value, SafeHtmlBuilder sb) {
if (value == null) {
return;
}
sb.appendEscaped(value.getName());
}
}
}
Thanks Ümit for your explanation.
I tried the close-reopen version.
I have replaced my refresh method with the methods below.
Adding is working but removing not.
Very strange the whole topic. I'm very suprised that these basic
functions are not really supported by GWT.
Can somebody give me more help to run a real working example.
public void refresh() {
closeReopenTreeNodes(cellTree.getRootTreeNode());
}
private void closeReopenTreeNodes(TreeNode node) {
if(node == null) {
return;
}
for(int i = 0; i < node.getChildCount(); i++) {
if(node.getChildValue(i).equals(this)){
if(node.getParent() != null){
node.getParent().setChildOpen(i, false);
//node.getParent().setChildOpen(i, true);
}
node.setChildOpen(i, false);
node.setChildOpen(i, true);
}
TreeNode child = node.setChildOpen(i, node.isChildOpen(i));
closeReopenTreeNodes(child);
}
}
Here my third try:
This way is recommended by gwt-commiter.
Please see following issue:
http://code.google.com/p/google-web-toolkit/issues/detail?id=7160
Current status:
* Adding is possible
* Removing is possible if not last child!
So, last open point, refresh the tree if last open child!
package com.test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.cell.client.AbstractCell;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.cellview.client.CellTree;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.gwt.view.client.TreeViewModel;
public class MyCelltreeTest2 implements EntryPoint {
private AbsolutePanel absolutePanel;
private CellTree cellTree;
private Button btnAdd;
private Button btnRemove;
private MyTreeModel myTreeModel;
private SingleSelectionModel<MyNode> selectionModelCellTree = null;
private Map<MyNode, ListDataProvider<MyNode>> mapDataProviders = null;
private ListDataProvider<MyNode> rootDataProvider = null;
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
rootPanel.add(getAbsolutePanel(), 0, 0);
}
private AbsolutePanel getAbsolutePanel() {
if (absolutePanel == null) {
absolutePanel = new AbsolutePanel();
absolutePanel.setSize("612px", "482px");
absolutePanel.add(getCellTree(), 0, 0);
absolutePanel.add(getBtnAdd(), 265, 428);
absolutePanel.add(getBtnRemove(), 336, 428);
}
return absolutePanel;
}
private CellTree getCellTree() {
if (cellTree == null) {
myTreeModel = new MyTreeModel();
cellTree = new CellTree(myTreeModel, null);
cellTree.setSize("285px", "401px");
}
return cellTree;
}
private Button getBtnAdd() {
if (btnAdd == null) {
btnAdd = new Button("Add");
btnAdd.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
myTreeModel.add(node, "Bla");
}
});
}
return btnAdd;
}
private Button getBtnRemove() {
if (btnRemove == null) {
btnRemove = new Button("Remove");
btnRemove.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
MyNode node = selectionModelCellTree.getSelectedObject();
myTreeModel.remove(node);
}
});
}
return btnRemove;
}
public class MyNode {
private String name;
private ArrayList<MyNode> childs; //nodes childrens
private MyNode parent; //track internal parent
public MyNode(String name) {
super();
parent = null;
this.name = name;
childs = new ArrayList<MyNode>();
}
public boolean hasChildrens() {
return childs.size()>0;
}
public ArrayList<MyNode> getList() {
return childs;
}
public MyNode getParent() {
return parent;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class MyTreeModel implements TreeViewModel {
public MyTreeModel() {
selectionModelCellTree = new SingleSelectionModel<MyNode>();
mapDataProviders = new HashMap<MyCelltreeTest2.MyNode, ListDataProvider<MyNode>>();
}
public void add(MyNode myparent, String name) {
MyNode child = new MyNode(name);
//root-node
if(myparent == null){
rootDataProvider.getList().add(child);
mapDataProviders.put(child, rootDataProvider);
}
else{
ListDataProvider<MyNode> dataprovider = mapDataProviders.get(myparent);
myparent.childs.add(child);
child.parent = myparent;
dataprovider.refresh();
}
}
public void remove(MyNode objToRemove) {
ListDataProvider<MyNode> dataprovider = mapDataProviders.get(objToRemove);
dataprovider.getList().remove(objToRemove);
// mapDataProviders.remove(objToRemove);
dataprovider.refresh();
dataprovider.flush();
if(objToRemove.parent != null){
ListDataProvider<MyNode> dataproviderParent = mapDataProviders.get(objToRemove.parent);
objToRemove.parent.childs.remove(objToRemove);
dataproviderParent.refresh();
dataproviderParent.flush();
}
else{
rootDataProvider.refresh();
rootDataProvider.flush();
}
}
#Override
public <T> NodeInfo<?> getNodeInfo(T value) {
if (value == null) { // root is not set
rootDataProvider = new ListDataProvider<MyNode>(new ArrayList<MyNode>());
MyCell cell = new MyCell();
return new DefaultNodeInfo<MyNode>(rootDataProvider, cell,
selectionModelCellTree, null);
} else {
MyNode myValue = (MyNode) value;
ListDataProvider<MyNode> dataProvider =
new ListDataProvider<MyNode>(myValue.childs);
MyCell cell = new MyCell();
for(MyNode currentNode : myValue.childs){
mapDataProviders.put(currentNode, dataProvider);
}
return new DefaultNodeInfo<MyNode>(dataProvider, cell,
selectionModelCellTree, null);
}
}
#Override
public boolean isLeaf(Object value) {
if (value instanceof MyNode) {
MyNode t = (MyNode) value;
if (!t.hasChildrens())
return true;
return false;
}
return false;
}
}
public class MyCell extends AbstractCell<MyNode> {
public MyCell() {
super();
}
#Override
public void render(Context context, MyNode value, SafeHtmlBuilder sb) {
if (value == null) {
return;
}
sb.appendEscaped(value.getName());
}
}
}
This is somehow a known problem with CellTree.
The reason for the refresh problem is that in the getNodeInfo() function you create a new ListDataProvider instance for each CellTree level.
The CellTree only updates/refreshes itself if you update the items in that ListDataProvider.
I believe that your removeMenu() and addSubMenu() functions add and remove items from the original list stored in your MyNode class but won't update the list in the corresponding ListDataProviders (you can try to check that in debug mode).
The reason why the CellTree is refreshed when you close and re
-open the nodes is because when you re-open the nodes the getNodeInfo() is called again and the whole CellTree structure will be constructed again (including the new menu or without the removed one respectively).
There are two possible solutions:
Keep a reference for each of the ListDataProviders somewhere and call remove/add on that list (although you do that I assume that the items are not really added/removed there).
Programatically close all nodes and re-open it.
Both are somehow a PITA to implement. Unfortunately there is no easy way around it.
I just clear the array of objects maintained in my data provider. I do this in onRangeChanged(final HasData<?> display). I guess I don't use a ListDataProvider here, I use something extending AbstractDataProvider<T> instead.
To add the node add it to your list within the onRangeChanged() method and then call updateRowData(). You can do this for a delete too.
I think I may have licked the problem...
Essentially I've extended and subclasses many parts of CellTree and have obtained an almost perfect working example. Too complex to document here, but suffice to say the solution involved using a node class where I stored the data provider within each node.
https://code.google.com/p/updatable-cell-tree/
In my CellTable, One column has ButtonCells. So I need to disable theses ButtonCells by clicking on a button which is located outside the CellTable.
You could create your own Button Cell class. For example:
import com.google.gwt.cell.client.AbstractSafeHtmlCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.text.shared.SafeHtmlRenderer;
import com.google.gwt.text.shared.SimpleSafeHtmlRenderer;
/**
* A {#link Cell} used to render a button.
*/
public class StyledButtonCell extends AbstractSafeHtmlCell<String> {
private String disabledString = "";
private boolean disabled = false;
/**
* Construct a new ButtonCell that will use a {#link SimpleSafeHtmlRenderer}.
*/
public StyledButtonCell() {
this(SimpleSafeHtmlRenderer.getInstance());
}
/**
* Construct a new ButtonCell that will use a given {#link SafeHtmlRenderer}.
*
* #param renderer a {#link SafeHtmlRenderer SafeHtmlRenderer<String>} instance
*/
public StyledButtonCell(SafeHtmlRenderer<String> renderer) {
super(renderer, "click", "keydown");
}
#Override
public void onBrowserEvent(Context context, Element parent, String value,
NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if ("click".equals(event.getType())) {
EventTarget eventTarget = event.getEventTarget();
if (!Element.is(eventTarget)) {
return;
}
if (parent.getFirstChildElement().isOrHasChild(Element.as(eventTarget))) {
// Ignore clicks that occur outside of the main element.
onEnterKeyDown(context, parent, value, event, valueUpdater);
}
}
}
#Override
public void render(Context context, SafeHtml data, SafeHtmlBuilder sb) {
sb.appendHtmlConstant("<button type=\"button\" tabindex=\"-1\"" + disabledString + ">");
if (data != null) {
sb.append(data);
}
sb.appendHtmlConstant("</button>");
}
#Override
protected void onEnterKeyDown(Context context, Element parent, String value,
NativeEvent event, ValueUpdater<String> valueUpdater) {
if (valueUpdater != null) {
valueUpdater.update(value);
}
}
public boolean isDisabled() {
return disabled;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
if (disabled) {
disabledString = "disabled=\"disabled\"";
} else {
disabledString = "";
}
}
}
Then use this with your cell table:
final StyledButtonCell buttonCell = new StyledButtonCell();
buttonColumn = new Column<SomeItem, String>(buttonCell) {
public String getValue(SomeItem object) {
// The value to display in the button.
return "Go";
}
};
To disable the button, simply call:
buttonCell.setDisabled(true);
table.redraw();
If you don't need to "rename" your button, you can also create your own based on Boolean abstract cell:
public class CustomButtonCell extends AbstractEditableCell<Boolean, Boolean> {
private static final SafeHtml ENABLED = SafeHtmlUtils.fromSafeConstant("<button type=\"button\" tabindex=\"-1\">");
private static final SafeHtml DISABLED = SafeHtmlUtils.fromSafeConstant("<button type=\"button\" tabindex=\"-1\" disabled=\"disabled\">");
private static final SafeHtml CLOSE_BRACKET = SafeHtmlUtils.fromSafeConstant("</button>");
private final boolean dependsOnSelection;
private final boolean handlesSelection;
private final String text;
public CustomButtonCell(final String text) {
this(false, false, text);
}
public CustomButtonCell(boolean dependsOnSelection, boolean handlesSelection, String text) {
super(BrowserEvents.CHANGE, BrowserEvents.CLICK, BrowserEvents.KEYDOWN);
this.dependsOnSelection = dependsOnSelection;
this.handlesSelection = handlesSelection;
this.text = text;
}
#Override
public boolean dependsOnSelection() {
return dependsOnSelection;
}
#Override
public boolean handlesSelection() {
return handlesSelection;
}
#Override
public boolean isEditing(Context context, Element parent, Boolean value) {
return false;
}
/**
* Based on CheckboxCell
*/
#Override
public void onBrowserEvent(Context context, Element parent, Boolean value,
NativeEvent event, ValueUpdater<Boolean> valueUpdater) {
String type = event.getType();
boolean enterPressed = BrowserEvents.KEYDOWN.equals(type)
&& event.getKeyCode() == KeyCodes.KEY_ENTER;
if (BrowserEvents.CHANGE.equals(type) || BrowserEvents.CLICK.equals(type) || enterPressed) {
InputElement input = parent.getFirstChild().cast();
Boolean isChecked = input.isChecked();
if (enterPressed && (handlesSelection() || !dependsOnSelection())) {
isChecked = !isChecked;
input.setChecked(isChecked);
}
if (value != isChecked && !dependsOnSelection()) {
setViewData(context.getKey(), isChecked);
} else {
clearViewData(context.getKey());
}
if (valueUpdater != null) {
valueUpdater.update(isChecked);
}
}
}
#Override
public void render(Context context, Boolean value, SafeHtmlBuilder sb) {
// Get the view data.
Object key = context.getKey();
Boolean viewData = getViewData(key);
if (viewData != null && viewData.equals(value)) {
clearViewData(key);
viewData = null;
}
if (value != null && ((viewData != null) ? viewData : value)) {
sb.append(ENABLED);
} else {
sb.append(DISABLED);
}
sb.append(SafeHtmlUtils.fromString(text));
sb.append(CLOSE_BRACKET);
}
}
and edit it by its "get value" mechanisme
Column<TestJSO, Boolean> revokeColumn = CellTableUtils.createColumn(new CustomButtonCell("Test"),
new GetValue<TestJSO, Boolean>() {
#Override
public Boolean getValue(TestJSO value) {
return value.isEnabled();
}
});
Good morning,
that was a nice little example to do early in the morning. Here you have my solution, I'm not saying, its the best but its working!
public class _57_DisableAllButtons implements EntryPoint {
boolean enable = false;
#Override
public void onModuleLoad() {
Button b1 = new Button("Button1");
Button b2 = new Button("Button2");
Button b3 = new Button("Button3");
RootPanel.get("nameFieldContainer").add(b1);
RootPanel.get("nameFieldContainer").add(b2);
RootPanel.get("nameFieldContainer").add(b3);
Button disableAll = new Button("Disabale all");
RootPanel.get("sendButtonContainer").add(disableAll);
disableAll.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
final RootPanel rootpannel = RootPanel.get("nameFieldContainer");
int widgetcount = rootpannel.getWidgetCount();
for (int i = 0; i < widgetcount; i++) {
Widget w = rootpannel.getWidget(i);
if(w instanceof Button){
((Button) w).setEnabled(enable);
}
}
enable = !enable;
}
});
}
}