I've been tasked with implementing sorting & filtering of data displayed in a GWT CellTable.
Thankfully GWT already supports sorting, but it looks like I'll have to hack together my own filtering support.
To be more precise, what I'm trying to support is similar to the filtering offered by Excel, whereby you can click on a drop-down menu in the column headers and (for example) click checkboxes that will allow you to filter the rows based on the values for the filtered column(s). A picture is worth a thousand words:
My question: any suggestions on how to go about implementing this in GWT 2.2? Is it even possible?
One option I'm thinking about is to pass in a custom Header object to CellTable.addColumn(). If it's possible, I'll add a ClickHandler to the Header, and then open a Popup that displays a widget for filtering. Not sure how to implement this without negatively affecting the sorting behavior.
Any suggestions gladly welcome.
Edit:
Thanks to John below, I've got the following FilterableHeader class that allows me to at least put an icon into the header. Unsure how to get a ClickHandler on that icon just yet, as the image is inserted via HTML rather than using GWT widgets.
public class FilterableHeader extends Header<String>
{
/**
* Image resources.
*/
public static interface Resources extends ClientBundle
{
ImageResource downArrow();
ImageResource upArrow();
}
private static final Resources RESOURCES = GWT.create(Resources.class);
private static final int IMAGE_WIDTH = 16;
private static final String DOWN_ARROW = makeImage(RESOURCES.downArrow());
private static final String UP_ARROW = makeImage(RESOURCES.upArrow());
private static String makeImage(ImageResource resource)
{
AbstractImagePrototype proto = AbstractImagePrototype.create(resource);
return proto.getHTML().replace("style='", "style='position:absolute;right:0px;top:0px;");
}
private String text;
public FilterableHeader(String text)
{
super(new ClickableTextCell());
this.text = text;
}
#Override
public String getValue()
{
return text;
}
#Override
public void render(Cell.Context context, SafeHtmlBuilder safe)
{
int imageWidth = IMAGE_WIDTH;
StringBuilder sb = new StringBuilder();
sb.append("<div style='position:relative;cursor:hand;cursor:pointer;");
sb.append("padding-right:");
sb.append(imageWidth);
sb.append("px;'>");
sb.append(UP_ARROW);
sb.append("<div>");
sb.append(text);
sb.append("</div></div>");
safe.append(SafeHtmlUtils.fromSafeConstant(sb.toString()));
}
}
Custom headers are what is used with GWT 2.1 to do sorting. The 2.1 bikeshed has examples that use custom headers and am using one for sorting until Mvp4g moves to 2.2. To enable the filtering, just add an image with its own click handler and you should be good - it won't trigger sort behavior when it's clicked on, just the rest of the header will.
table.addColumn(new MyColumn(new MyCell()), new MyFilterHeader());
For the actual filtering, if you're using the database model from the examples (the wrapper class for the ListDataProvider), then I'd think you'd just keep two lists - the filtered list that's assigned to the ListDataProvider, and the unfiltered list it's based on.
Hope that helps!
In your new sample code, you might want to try a CompositeCell with the ClickableTextCell inside it, along with an ActionCell for the filtering part - if you can stick an image into the ClickableTextCell, you should be able to in the ActionCell, plus it'll have the mouseup behavior you want.
I develop business applications where a typical database query might return hundreds or thousands of rows. Users find the excel-like filters and column sorts to be very helpful.
Hence I have implemented a class that extends ListDataProvider for use with a CellTable that supports client-side excel-like column filtering and sorting. In all other respects it behaves much like a ListDataProvider.
It depends on implementing a the following ColumnAccessor interface to provide a symbolic name for for each column in the CellTable, to provide access to column-level data for sorting and filtering, a Comparator for the column for sorting, and display label for the header. Following is the ColumnAccessor class. It assumes that you have some sort of Data Transfer Object <T> that models the rows.
/**
* Interface to provide access to a specific
* column within a data row.
* #param <T> Object that contains the column
* values in a cell table row. Typically a Data Transfer Object.
*/
public interface ColumnAccessor<T> {
/**
* Filter display value for blank/null column values
*/
public final String FILTER_SELECTOR_BLANK = "{Blank}";
/**
* Returns A row-unique symbolic name for the column. This name is
* used as a Map key to access the ColumnAccessor instance by
* name for filtering and sorting.
* #return
*/
public String getColumnName();
/**
* Returns text label to appear as column header in CellTable.
* #return
*/
public String getLabel();
/**
* Returns value of the column as a String
* #param t Object that models the column values in a
* cell table row (Typically a Data Transfer Object)
* #return
*/
public String getValue(T t);
/**
* Returns Comparator for sorting data rows and for sorting
* discrete values that appear in a filter's select/option list.
* While the getValue() method always returns a String,
* these comparators should sort the column's values in
* consideration for the data type (for example, dates sorted
* as dates, numbers sorted as numbers, strings sorted as strings).
* #return
*/
public Comparator comparator();
}
Following is the FilterSortDataProvider class:
import com.google.gwt.cell.client.SelectionCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.Header;
import com.google.gwt.view.client.ListDataProvider;
import java.util.*;
/**
* Class that extends a ListDataProvider but adds "Excel-Like" column filters and also
* includes click on column heading sorts.
* #param <T> Object that contains the column values in a cell table row. Typically a Data Transfer Object.
*/
public class FilterSortDataProvider<T> extends ListDataProvider {
private List<T> rows;
private List<T> filteredSortedRows;
public Map<String, DataColumn> dataColumnMap = new HashMap<String, DataColumn>();
private String lastSortColumn = "*";
private int lastSortDirection = 0;
/**
* Constructs the DataProvider and columns
* #param rows Collection of objects that contain column data for cell table rows, typically
* Data Transfer Objects.
* #param columnAccessors List of ColumnAccessor instances for each column that will appear in
* the cell table. Each accessor will render a sortable, filterable column header
* and provides access to column-level data.
*/
public FilterSortDataProvider(Collection<T> rows, List<ColumnAccessor> columnAccessors) {
this.rows = new ArrayList<T>(rows);
this.filteredSortedRows = new ArrayList<T>();
Iterator<ColumnAccessor> columnAccessorIterator = columnAccessors.iterator();
while (columnAccessorIterator.hasNext()) new DataColumn(columnAccessorIterator.next());
// Initialize filters
filter();
}
/**
* Returns defensive copy of the current collection of filtered/sorted data rows
* #return
*/
public List<T> getFilteredSortedRows() {
return new ArrayList(filteredSortedRows);
}
/**
* Returns a CellTable Header for the named column for use when setting up the CellTable (ie:
* used as Header value in cellTable.addColumn(TextColumn, Header) call. The header includes
* the columnAccessor.getLabel() value as a click-to-sort header label, and a drop-down filter
* where the options include all available values.
* #param columnName Same value as returned by this columns ColumnAccessor.getColumnName()
* #return
*/
public Header getColumnHeader(final String columnName) {
DataColumn column = dataColumnMap.get(columnName);
return (column != null ? new FilteredCellTableHeader(column) : null);
}
/**
* Called when user clicks on column header label. Repeated clicks on the same column header will
* reverse the sort direction. Can also be called prior to display of CellTable to establish an initial
* sort order.
* #param sortColumnName
*/
public void sort(String sortColumnName) {
if (!sortColumnName.equals("*")) {
DataColumn column = dataColumnMap.get(sortColumnName);
if (column != null) {
// Sort ascending
Collections.sort(this.filteredSortedRows, column);
// Re-Sort of same column
if (sortColumnName.equals(lastSortColumn)) {
lastSortDirection *= -1;
}
else {
lastSortDirection = 1;
lastSortColumn = sortColumnName;
}
if (lastSortDirection == -1) Collections.reverse(filteredSortedRows);
}
}
this.setList(filteredSortedRows);
}
/**
* Optional call to pre-set filter before initial display of CellTable
* #param columnName
* #param value
*/
public void filter(String columnName, String value) {
DataColumn column = dataColumnMap.get(columnName);
if (column != null) column.filter(value);
}
/**
* Filters the rows based on all of the filters, and re-builds the filter drop-down
* options.
*/
private void filter() {
// Build collection of rows that pass all filters
filteredSortedRows = new ArrayList<T>();
Iterator<T> rowIterator = this.rows.iterator();
while (rowIterator.hasNext()) {
T row = rowIterator.next();
if (rowPassesFilter(row, null)) filteredSortedRows.add(row);
}
// Build filter select/option list for each column based on rows
// that pass all filters EXCEPT for the column in question.
Iterator<DataColumn> columnIterator = dataColumnMap.values().iterator();
while (columnIterator.hasNext()) {
DataColumn column = columnIterator.next();
Set<String> optionsSet = new HashSet<String>();
rowIterator = this.rows.iterator();
while (rowIterator.hasNext()) {
T row = rowIterator.next();
if (rowPassesFilter(row, column)) {
optionsSet.add(column.filterOptionValue(row));
}
}
// Sort the options using the ColumnAccessor's comparator
List<String> optionsList = new ArrayList<String>(optionsSet);
Collections.sort(optionsList, column.comparator());
// Make blank option (if any) the last entry in the option list
if (optionsList.contains(ColumnAccessor.FILTER_SELECTOR_BLANK)) {
optionsList.remove(ColumnAccessor.FILTER_SELECTOR_BLANK);
optionsList.add(ColumnAccessor.FILTER_SELECTOR_BLANK);
}
// Add the wild-card "All" as the first entry in the option list
optionsList.add(0, "*");
// Set the new list of options in the column
column.filterOptions = optionsList;
}
// Re-sort the data with consideration for the current sort column and direction
lastSortDirection *= -1;
sort(lastSortColumn);
}
/**
* Returns true if the specified row passes all column filters.
* #param row Data row to test
* #param columnToIgnore When specified, this column is assumed to allow the row
* to pass the filter. This is used when building the list
* of filter select/option values.
* #return
*/
private boolean rowPassesFilter(T row, DataColumn columnToIgnore) {
Iterator<DataColumn> columnIterator = dataColumnMap.values().iterator();
boolean passes = true;
while (columnIterator.hasNext() && passes) {
DataColumn column = columnIterator.next();
if (column != columnToIgnore) {
passes = column.rowPassesFilter(row);
}
}
return passes;
}
/**
* Inner class that models a CellTable column, its ColumnAccessor, current filter value,
* and current filter option values.
*/
public class DataColumn implements Comparator<T> {
private String filterValue = "*";
private List<String> filterOptions = new ArrayList<String>();
private ColumnAccessor columnAccessor;
/**
* Constructs a filterable, sortable column
* #param columnAccessor
*/
public DataColumn(final ColumnAccessor columnAccessor) {
this.columnAccessor = columnAccessor;
FilterSortDataProvider.this.dataColumnMap.put(columnAccessor.getColumnName(), this);
}
/**
* Returns symbolic name of column
* #return
*/
public String getName() {
return this.columnAccessor.getColumnName();
}
/**
* Returns display label for column header
* #return
*/
public String getLabel() {
return columnAccessor.getLabel();
}
/**
* Returns value of column
* #param row
* #return
*/
public String getValue(T row) {
return columnAccessor.getValue(row);
}
/**
* Returns comparator define in ColumnAccessor for use when sorting
* data rows and for sorting filter options.
* #return
*/
public Comparator comparator() {
return columnAccessor.comparator();
}
/**
* Called when user changes the value of a column filter
* #param filterValue
*/
public void filter(String filterValue) {
if (this.filterOptions.contains(filterValue)) {
this.filterValue = filterValue;
FilterSortDataProvider.this.filter();
}
}
/**
* Called when user clicks on column label to sort rows
*/
public void sort() {
FilterSortDataProvider.this.sort(this.columnAccessor.getColumnName());
}
/**
* Used to sort data rows. Uses comparator specified in ColumnAccessor.
* #param row1
* #param row2
* #return
*/
public int compare(T row1, T row2) {
return comparator().compare(getValue(row1), getValue(row2));
}
/**
* Returns true if specified row passes this column's filter
* #param row
* #return
*/
public boolean rowPassesFilter(T row) {
return filterValue.equals("*") || filterValue.equals(filterOptionValue(row));
}
/**
* Returns value to appear in filter options list. Null or "blank" values appear in options
* list as {Blank}.
* #param row
* #return
*/
private String filterOptionValue(T row) {
String value = getValue(row);
return (value == null || value.trim().length() == 0 ? ColumnAccessor.FILTER_SELECTOR_BLANK : value);
}
/**
* Renders Html Select/Options tag for column filter
* #return
*/
public String toHtmlSelect() {
StringBuffer sb = new StringBuffer();
sb.append("<select size='1' style='width: 100%;'>");
Iterator<String> opts = filterOptions.iterator();
while (opts.hasNext()) {
String escapedOption = SafeHtmlUtils.htmlEscape(opts.next());
sb.append("\t<option value='" + escapedOption);
sb.append((escapedOption.equals(filterValue) ? "' SELECTED>" : "'>"));
sb.append(escapedOption + "</option>\n");
}
sb.append("</select>\n");
return sb.toString();
}
}
/**
* Inner class Header wrapper for FilteredSortedCellTableHeaderCell
*/
public class FilteredCellTableHeader extends Header {
public FilteredCellTableHeader(DataColumn column) {
super(new FilteredSortedCellTableHeaderCell(column));
}
public Object getValue() {
return null;
}
}
/**
* CellTable SelectionCell that includes filter and sort controls, renders controls, and
* handles onBrowserEvent()
*/
private class FilteredSortedCellTableHeaderCell extends SelectionCell {
private DataColumn column;
public FilteredSortedCellTableHeaderCell(final DataColumn column) {
super(new ArrayList<String>());
this.column = column;
}
/**
* Renders Html Submit button as sort control, and Html Select/Option tag for filter.
* #param context
* #param value
* #param sb
*/
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
String sortButton = "<input type='submit' value='" + SafeHtmlUtils.htmlEscape(column.getLabel()) +
"' style='text-align: center; width: 100%; background: none; border: none; font-weight: bold;'>";
sb.appendHtmlConstant(sortButton);
sb.appendHtmlConstant("<br>");
sb.appendHtmlConstant(column.toHtmlSelect());
}
/**
* Detects filter and sort user interaction events
* #param context
* #param parent
* #param value
* #param event
* #param valueUpdater
*/
#Override
public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
String type = event.getType();
Element element = event.getEventTarget().cast();
String tagName = element.getTagName();
// Filter selection changed
if ("change".equals(type) && tagName.equals("SELECT")) {
// Set filter value and call filter routine
SelectElement se = (SelectElement)element;
String filterValue = se.getOptions().getItem(se.getSelectedIndex()).getValue();
column.filter(filterValue);
}
// Click on sort button
else if (type.equals("focus") && tagName.equals("INPUT")) {
column.sort();
}
}
}
}
I hope that this might be of help to someone.
I used the mouse-click position to add custom click events to column headers. In other words, you can set it up so that if the user clicks in the 'general area' where the image is supposed to be, you can show a filtering screen.
Here's an example where I have it ignoring click events for a text field I added:
if(col.isFilterable()){
if (event.getClientY() > (getInputElement(parent).getAbsoluteTop() - 2) && event.getClientY() < (getInputElement(parent).getAbsoluteBottom() + 2)) {
//ignore on click in area of the text field
event.preventDefault();
} else {
//sort if user clicks anywhere else
trySort(parent);
}
And the because cell listens for 'keyup' events separately, the filter is executed when the user hits enter (while the cell is focused).
if(event.getKeyCode()==13){
event.preventDefault();
handleSetFilterValue(parent);
tryFilter();
}
Related
I'm having a little trouble figuring out how to appropriately make an object which holds not only strings and ints, but also string arrays and int arrays. I currently have this
public class Gamebook {
Section[] sections = new Section[6];
String storyText;
String[] choiceText;
int[] choiceSections;
public Gamebook() {
storyText = "";
choiceText = new String[1];
choiceSections = int[];
}
}
If I understand correctly this will create an array of section objects, 6 of those, as well as allow for my section object to have a storyText string, a choiceText string array, and a choiceSections int array.
However, this seems hard coded, I need to be able to read through a txt file which I have, pull out the story text and other necessary information, and assign those pieces to each section object. Im finding little to no sucess and feel I may be lacking an understanding of the object orientation of Java, but after watching a couple videos on youtube most of them didn't handle objects with multiple attributes, and those that did manually set them themselves instead of using methods to parse information to fill in the "blanks".
Use Araylist it will store your all objects check out this tutorial it will help you or this one
Here is an example you can play with. This uses Lists instead of Arrays. You can replace the Lists with Arrays, but you have to set a size for the Arrays in the constructor. Also, adding the Arrays will create new problems that you will have to handle.
AlbumInfo Class:
import java.util.*;
/**
*
* #author Sedrick
*/
public class AlbumInfo {
private String albumName;
private String artist;
private List<String> tracksTitle;
private List<String> tracksLength;
public AlbumInfo()
{
albumName = "Add Album Name";
artist = "Add Artist Name";
tracksTitle = new ArrayList();
tracksLength = new ArrayList();
}
/**
* #return the albumName
*/
public String getAlbumName()
{
return albumName;
}
/**
* #param albumName the albumName to set
*/
public void setAlbumName(String albumName)
{
this.albumName = albumName;
}
/**
* #return the artist
*/
public String getArtist()
{
return artist;
}
/**
* #param artist the artist to set
*/
public void setArtist(String artist)
{
this.artist = artist;
}
/**
* #return the tracksTitle
*/
public List<String> getTracksTitle()
{
return tracksTitle;
}
/**
* #param tracksTitle the tracksTitle to set
*/
public void addTrackTitle(String trackTitle)
{
this.tracksTitle.add(trackTitle);
}
/**
* #return the tracksLength
*/
public List<String> getTracksLength()
{
return tracksLength;
}
/**
* #param tracksLength the tracksLength to set
*/
public void addTrackLength(String trackLength)
{
this.tracksLength.add(trackLength);
}
}
Main Test Class:
import java.util.*;
/**
*
* #author Sedrick
*/
public class AlbumTest {
static final String[] trackTitles = {"Ambitionz Az a Ridah", "All Bout U", "Skandalouz", "Got My Mind Made Up", "How Do U Want It", "2 of Amerikaz Most Wanted", "No More Pain", "Heartz of Men", "Life Goes On", "Only God Can Judge Me", "Tradin' War Stories", "California Love(Remix)", "I Ain't Mad at Cha", "What'z Ya Phone #"};
static final String[] trackLength = {"4:39", "4:37", "4:09", "5:14", "4:47", "4:07", "6:14", "4:43", "5:02", "4:57", "5:29", "6:25", "4:53", "5:10"};
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
// Add album info
AlbumInfo allEyesOnMeDiscOne = new AlbumInfo();
allEyesOnMeDiscOne.setAlbumName("All Eyes On Me");
allEyesOnMeDiscOne.setArtist("Tupac");
for (int i = 0; i < trackTitles.length; i++) {
allEyesOnMeDiscOne.addTrackTitle(trackTitles[i]);
}
for (String entry : trackLength) {
allEyesOnMeDiscOne.addTrackLength(entry);
}
//Print album info
System.out.println("Album Name: " + allEyesOnMeDiscOne.getAlbumName());
System.out.println("Album Artist: " + allEyesOnMeDiscOne.getArtist());
List albumTitles = allEyesOnMeDiscOne.getTracksTitle();
List albumTitlesLength = allEyesOnMeDiscOne.getTracksLength();
for (int i = 0; i < albumTitles.size(); i++) {
System.out.println("Title: " + albumTitles.get(i) + " Length: " + albumTitlesLength.get(i));
}
}
}
The problem with arrays is that their size is fixed. Meaning: you need to know the number of elements when writing
someArray = new Whatever[...
This means: when parsing information from files you have two options:
start with a large empty array, and keep track of the elements you put into those empty slots. You might have to copy things into a larger array when running out of space. You can copy the entries in an perfectly sized array in the end.
you turn to Java List and ArrayList - which allow for dynamically adding and removing of elements.
I need to add an Object to an ordered ArrayList depending on an attribute inside of the Object. I know how to use the .add method to add the object but I don't know how to search for the right place for it using the compareTo() method. And also I need to remove an Object from the ArrayList if the Object contains a certain String but I cant figure out how to access the Object attributes from the ArrayList.
Realtor Object
/**
* Constructor for Realtor object using parameter
* #param readData - array of data from line
*/
public Realtor(String[]readData){
licenseNumber = readData[2];
firstName = readData[3];
lastName = readData[4];
phoneNumber = readData[5];
commission = Double.parseDouble(readData[6]);
}
RealtorLogImpl
public class RealtorLogImpl {
private ArrayList<Realtor> realtorList;
/**
* Add Realtor object to ordered list
* #param obj - Realtor object
*/
public void add(Realtor obj){
//needs to be added into correct place depending on Realtor licenseNumber
realtorList.add(obj);
}
/**
* Delete Realtor object from list if license matches
* and return true if successful
* #param license
* #return
*/
public boolean remove (String license){
//need to remove Realtor with specific licenseNumber and return true if successful
}
I'm assuming you are using java 8. Some of these things have not been implemented in java 7 so keep that in mind.
First, to remove the items I would recommend using the removeif() method on the arraylist. This takes a lambda expression which could be something like x -> x.getString().equals("someString").
Second, You could add the object to the array then simply sort the array afterwards. You would just have to write a comparator to sort it by.
Here is some basic code; I have no compiler here, so you might find small errors/typos.
I'm sure there are better classes you can use instead of managing your own ordered list.
To insert:
public bool add(Realtor obj) {
int idx = 0;
for (Realtor s : realtorList) {
if (s.licenseNumber.equals(item.licenseNumber)) {
return false; // Already there
}
if (s.licenseNumber.compareTo(item.licenseNumber) > 0) {
orderedList.add(idx, item);
return true; // Inserted
}
idx++;
}
orderedList.add(item);
return true; // Appended
}
To delete:
public bool deleteItem(String license) {
int idx = 0;
for (Realtor s : realtorList) {
if (s.licenseNumber.equals(license)) {
realtorList.remove(idx);
return true; // Removed
}
}
return false; // Not found
}
To answer your question check the following snippet (requires Java 8) and adapt on your demand:
public static void main(String[] args) {
final List<String> list = new ArrayList<>();
list.add("Element 1");
list.add("Element 2");
list.add("Element 3");
/*
* Insert at a specific position (add "Element 2.5" between "Element 2" and "Element 3")
*/
Optional<String> elementToInsertAfter = list.stream().filter(element -> element.equals("Element 2")).findFirst();
if(elementToInsertAfter.isPresent()) {
list.set(list.indexOf(elementToInsertAfter.get()) + 1, "Element 2.5");
}
/*
* Remove a particular element (in this case where name equals "Element 2")
*/
list.removeIf(element -> element.equals("Element 2"));
}
#add(element) just adds an element to the list. In case of an ArrayList it's added at the end. If you want to insert an element at a particular position you need to use #set(index,element)
But instead of inserting your element at a particular position manually you should maybe use a comparator instead. See java.util.List.sort(Comparator<? super E> e)
When I try to compile I get an error message for my makeUnion method and I'm guessing I will get one for makeIntersection too. I'm not sure why this is, or how to implement makeUnion if I want to add a set interface to the new set. Can someone explain to me what I am doing wrong?
public class Set<T> implements SetInterface<T>
{
private HashSet<T> set;
/**
* Constructs a new empty set.
*/
public Set () {
set = new HashSet <>();
}
/**
* Constructs a new set containing the elements in the specified collection.
* Default load factor of 0.75 and initial capacity of 50.
*
* #param c- the collection whose elements are to be place into this set
*/
public Set(Collection <? extends T> c) {
set = new HashSet<>(Math.max((int) (c.size()/.75f) + 1, 50));
set.addAll(c);
}
/**
* Constructs a new empty set. Default load factor of 0.75.
*
* #param initialCapacity- the initial capacity of the hash table
*/
public Set(int initialCapacity) {
set = new HashSet <>(initialCapacity);
}
/**
* Constructs a new empty set.
* Hashmap has specified initial capacity and specified load factor.
*
* #param initialCapacity- the initial capacity of the hash table
* loadFactor- the load factor of the hash map
*/
public Set(int initialCapacity, float loadFactor) {
set = new HashSet <>(initialCapacity, loadFactor);
}
/**
* Add an item of type T to the interface Duplicate items will not be
* added to the set.
*
* #param itemToAdd - what to add.
*/
public void add(T itemToAdd) {
set.add(itemToAdd);
}
/**
* Removes an item from the set ( if the item is in the set) If the item is not
* in the set this operation does nothing
*
* #param item to remove.
*/
public void remove( T itemToDelete) {
set.remove(itemToDelete);
}
/**
* Return if the SetInterface contains an item
*
* #param itemToCheck. The item you are looking for
* #return true if found. False if not found.
*/
public boolean contains( T itemToCheck) {
return set.contains(itemToCheck);
}
/**
* Make a union of two sets. We add all items in either set to a new set and
* return the new set.
*
* #param the 'other' set to add to our set.
* #return A new set which is the union of the two sets.
*/
public Set<T> makeUnion( SetInterface<T> otherSet) {
return set.addAll(otherSet);
}
/**
* Make an intersection of two sets. We add create a new set which only has
* items in it that are contained in both sets.
*
* #param the 'other' set to intersect with
* #return A new set which is the intersection of the two sets.
*/
public Set<T> makeIntersection( SetInterface<T> otherSet) {
return set.retainAll(otherSet);
}
/**
* Return an iterator for the set. This is used to walk thought all elements
* in the set
*
* #return The iterator
*/
public Iterator<T> getIterator() {
return set.iterator();
}
/**
* Tell the caller how many elements are in the set
*
* #return int with the number of elements
*/
public int size() {
return set.size();
}
}
interface SetInterface<T> {
void add(T itemToAdd);
public void remove( T itemToDelete);
public boolean contains( T itemToCheck);
public Set<T> makeUnion( SetInterface<T> otherSet);
public Iterator<T> getIterator();
public int size();
HashSet<T> getSet();
}
public class Set<T> implements SetInterface<T>
{
private HashSet<T> set;
public HashSet<T> getSet() {
return set;
}
/**
* Constructs a new empty set.
*/
public Set () {
set = new HashSet <>();
}
/**
* Constructs a new set containing the elements in the specified collection.
* Default load factor of 0.75 and initial capacity of 50.
*
* #param c- the collection whose elements are to be place into this set
*/
public Set(Collection <? extends T> c) {
set = new HashSet<>(Math.max((int) (c.size()/.75f) + 1, 50));
set.addAll(c);
}
/**
* Constructs a new empty set. Default load factor of 0.75.
*
* #param initialCapacity- the initial capacity of the hash table
*/
public Set(int initialCapacity) {
set = new HashSet <>(initialCapacity);
}
/**
* Constructs a new empty set.
* Hashmap has specified initial capacity and specified load factor.
*
* #param initialCapacity- the initial capacity of the hash table
* loadFactor- the load factor of the hash map
*/
public Set(int initialCapacity, float loadFactor) {
set = new HashSet <>(initialCapacity, loadFactor);
}
/**
* Add an item of type T to the interface Duplicate items will not be
* added to the set.
*
* #param itemToAdd - what to add.
*/
public void add(T itemToAdd) {
set.add(itemToAdd);
}
/**
* Removes an item from the set ( if the item is in the set) If the item is not
* in the set this operation does nothing
*
* #param item to remove.
*/
public void remove( T itemToDelete) {
set.remove(itemToDelete);
}
/**
* Return if the SetInterface contains an item
*
* #param itemToCheck. The item you are looking for
* #return true if found. False if not found.
*/
public boolean contains( T itemToCheck) {
return set.contains(itemToCheck);
}
/**
* Make a union of two sets. We add all items in either set to a new set and
* return the new set.
*
* #param the 'other' set to add to our set.
* #return A new set which is the union of the two sets.
*/
public Set<T> makeUnion( SetInterface<T> otherSet) {
set.addAll(new java.util.ArrayList<T>(otherSet.getSet()));
return this;
}
/**
* Return an iterator for the set. This is used to walk thought all elements
* in the set
*
* #return The iterator
*/
public Iterator<T> getIterator() {
return set.iterator();
}
/**
* Tell the caller how many elements are in the set
*
* #return int with the number of elements
*/
public int size() {
return set.size();
}
}
Scenario:
I've two reports: Main Report (let's call it, A) and sub-report (let's call it, B).
Report A contains sub-report B at the detail band, so sub-report B is displayed for each element at the Report A datasource. Sub-report B also returns a variable to the Main report A.
What I want is to sum those return values from sub-report B and totalize them at the Main report summary.
To do that, I have tried to create a new report variable that sum those returns values... Something like this:
However, I've found that such variables expression are always evaluated before the band detail is rendered, so I always miss the first sub-report return value...
Sadly, the evaluation time (as this link says) cannot be changed on those kind of variables, so I'm stuck...
After been struggling with this for some hours... and searching the internet for a solution... I came with a Workaround (the enlightening forums were these ones: one and two).
First, you need to define a java Class Helper that allows you calculate some arithmetic operation, in my case a Sum operation. I defined these classes:
package reports.utils;
import java.util.Map;
/**
* Utility that allows you to sum Integer values.
*/
public class SumCalculator {
/**
* Stores a map of {#code SumCalculator} instances (A Map instance per thread).
*/
private static final ThreadLocalMap<String, SumCalculator> calculatorsIndex = new ThreadLocalMap<>();
/**
* The sum total.
*/
private int total = 0;
/**
* No arguments class constructor.
*/
private SumCalculator() {
super();
}
/**
* Instance a new {#code SumCalculator} with the given ID.
*
* #param id {#code SumCalculator}'s ID
* #return the new {#code SumCalculator} instance
*/
public static SumCalculator get(String id) {
Map<String, SumCalculator> map = calculatorsIndex.get();
SumCalculator calculator = map.get(id);
if (calculator == null) {
calculator = new SumCalculator();
map.put(id, calculator);
}
return calculator;
}
/**
* Destroy the {#code SumCalculator} associated to the given ID.
*
* #param id {#code SumCalculator}'s ID
* #return {#code null}
*/
public static String destroy(String id) {
Map<String, SumCalculator> map;
map = calculatorsIndex.get();
map.remove(id);
if (map.isEmpty()) {
calculatorsIndex.remove();
}
return null;
}
/**
* Resets the {#code SumCalculator} total.
*
* #return {#code null}
*/
public String reset() {
total = 0;
return null;
}
/**
* Adds the given integer value to the accumulated total.
*
* #param i an integer value (can be null)
* #return {#code null}
*/
public String add(Integer i) {
this.total += (i != null) ? i.intValue() : 0;
return null;
}
/**
* Return the accumulated total.
*
* #return an Integer value (won't be null, never!)
*/
public Integer getTotal() {
return this.total;
}
}
package reports.utils;
import java.util.HashMap;
import java.util.Map;
/**
* Thread Local variable that holds a {#code java.util.Map}.
*/
class ThreadLocalMap<K, V> extends ThreadLocal<Map<K, V>> {
/**
* Class Constructor.
*/
public ThreadLocalMap() {
super();
}
/* (non-Javadoc)
* #see java.lang.ThreadLocal#initialValue()
*/
#Override
protected Map<K, V> initialValue() {
return new HashMap<>();
}
}
Second, at your jasper report, you need to define four text fields:
1) A text field that iniatializes your calculator; it should be (ideally) at the title section of the report and should have an expression like this: SumCalculator.get("$V{SUB_REPORT_RETURN_VALUE}").reset(). This text field should have the evaluation time: NOW.
2) A text field that calls the increment function (i.e. SumCalculator.get("$V{SUB_REPORT_RETURN_VALUE}").add($V{SUB_REPORT_RETURN_VALUE}). This text field will reside at your detail band, after the subreport element; and it should have the evaluation time: BAND (this is very important!!)
3) A text field that prints the calculator total. This text field will reside at your summary band, it will evaluate to NOW. Its expression will be: SumCalculator.get("$V{SUB_REPORT_RETURN_VALUE}").getTotal()
4) A text field that destroy the calculator. This text field will also reside at your summary band and must appear after the text field 3. The text field should have an expression like: SumCalculator.destroy("$V{SUB_REPORT_RETURN_VALUE}"). This text field should have the evaluation time: NOW.
Also, the text fields: 1, 2, and 4, should have the attribute "Blank when Null", so they will never be printed (that's why those java operations always return null).
And That's it. Then, your report can look something like this:
if i understand the problem, you can not summarize the amount returned by the sub report in the main report, i had the same problem and i solved in this way.
1.- Create a class which extends from net.sf.jasperreports.engine.JRDefaultScriptlet. and override the method beforeReportInit()
this is the code from this class.
package com.mem.utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.jasperreports.engine.JRDefaultScriptlet;
public class SumarizacionSubtotales extends JRDefaultScriptlet {
private final Log log = LogFactory.getLog(getClass());
private Double total;
public Double getTotal() {
return total;
}
public Double add(Double cantidad) {
if(log.isDebugEnabled())log.debug("AGREGANDO LA CANTIDAD : " + cantidad);
this.total += cantidad;
return cantidad;
}
#Override
public void beforeReportInit() throws JRScriptletException {
if(log.isDebugEnabled())log.debug("beforeReportInit");
total = 0.0D;
}
}
2.- add your project's jar in your ireport's classpath.
3.- Replace the class of the REPORT scriptlet.
in the properties with your class.
3.- add in the group footer where you want to print the value returned by the sub-report a textfield with the following expression.
$P{REPORT_SCRIPTLET}.add( $V{sum_detalles} )
In this case $V{sum_detalles} is a variable in the main report which contains the value returned by the sub-report.
4.- Add in the Last page footer another textfield with the following expression.
$P{REPORT_SCRIPTLET}.getTotal()
Can somebody explains how to use the GWT cell tree. I am trying to google it but not finding any valuable tutorial??
Thanks
Try;
Google Example 1
includes onModuleLoad method. :)
For those who don't like the google showcase example like me can use this example;
http://google-web-toolkit.googlecode.com/svn/javadoc/2.1/com/google/gwt/user/cellview/client/CellTree.html
you can just copy paste it and it works.
"
Trivial example
public class CellTreeExample implements EntryPoint {
/**
* The model that defines the nodes in the tree.
*/
private static class CustomTreeModel implements TreeViewModel {
/**
* Get the {#link NodeInfo} that provides the children of the specified
* value.
*/
public <T> NodeInfo<?> getNodeInfo(T value) {
/*
* Create some data in a data provider. Use the parent value as a prefix
* for the next level.
*/
ListDataProvider<String> dataProvider = new ListDataProvider<String>();
for (int i = 0; i < 2; i++) {
dataProvider.getList().add(value + "." + String.valueOf(i));
}
// Return a node info that pairs the data with a cell.
return new DefaultNodeInfo<String>(dataProvider, new TextCell());
}
/**
* Check if the specified value represents a leaf node. Leaf nodes cannot be
* opened.
*/
public boolean isLeaf(Object value) {
// The maximum length of a value is ten characters.
return value.toString().length() > 10;
}
}
public void onModuleLoad() {
// Create a model for the tree.
TreeViewModel model = new CustomTreeModel();
/*
* Create the tree using the model. We specify the default value of the
* hidden root node as "Item 1".
*/
CellTree tree = new CellTree(model, "Item 1");
// Add the tree to the root layout panel.
RootLayoutPanel.get().add(tree);
}
}
"