I currently have a CellTable which I expanded with a filter/paging/sorting.
I am working on adding more features too.
One things that bugs me though, is when you set the limit for a page to be for example 10.
If an object doesn't have 10 rows and only has 5 and you switch between them then the table will jump up and down if positioned in the center.
Is there an elegant way, that when the pageSize is set to 10, then if there are less than 10 rows in a table, empty rows will be added?
The ways I have tried so far effect my filtering/sorting/paging, which I do not want.
EDIT 1:
final AsyncDataProvider<Row> provider = new AsyncDataProvider<Row>() {
#Override
protected void onRangeChanged(HasData<Row> display) {
int start = display.getVisibleRange().getStart();
int end = start + display.getVisibleRange().getLength();
end = end >= rows.getFilterList().size() ? rows.getFilterList().size() : end;
List<Row> sub = rows.getFilterList().subList(start, end);
System.out.println("Checking if extra rows needed: " +
table.getVisibleItems().size());
for(int i = table.getVisibleItems().size(); i<10; i++){
System.out.println("ADDED EMPTY ROW");
sub.add(new Row());
}
updateRowData(start, sub);
}
};
I had the same problem. I solved it by adding null rows. Basically, you need to override onRangeChange(HasData<T> display) in your DataProvider to add null to the list representing the range asked for. You add enough null to fill the range you want.
#Override
protected void onRangeChanged(HasData<T> display){
Range range = display.getVisibleRange();
List<T> listOfObjects = getRange(range);
for(int i=listOfObject.size(); i<range.getLength(); i++){
listOfObjects.add(null);
}
updateRowData(range.getStart(), listofObjects);
}
In the above code getRange() returns a list containing the objects that are requested (within the range)
You'll also have to add some CSS to give a height to rows. If you don't specify the height then the rows will be very small so they won't pad the whole table.
It's disappointing that GWT doesn't have support for this type of padding. This way was the best one that I found but unfortunately when your row heights differ within a table it makes everything harder.
Paging
With the solution above you'll need to have a custom Pager if you want paging. You can extend your AsyncDataProvider with method like getVisibleRowCount() this will return how many objects in the current sublist are actually real data (not null). Then you can give a reference of the AsyncDataProvider to the Pager and override the createText() method:
#Override
protected String createText(){
return getPageStart() + " - " + dataProvider.getVisibileRowCount() + " of "
+ dataProvider.getTotalRowCount();
}
Related
I use iText 7.0.4.0 with my .net application to generate pdfs. But inner tables overflow when the text is long.
Outer table has 10 columns with green border and seems it has rendered fine as per the image below. Each Outer table cell contains one table with one cell inside it.But Inner Table cell has overflown when the paragraph text is large.
I use iText in a large Forms building product. Hence I've recreated the issue with simple scenario and the code is given below. Please note that the number of columns are not fixed in real usage.
Could anyone please show me the correct path to achieve this?
Here is the C# Code
private Table OuterTable()
{
var columns = GetTableColumnWidth(10);
var outerTable = new Table(columns, true);
outerTable.SetWidthPercent(100);
for (int index = 0; index < columns.Length; index++)
{
Cell outerTableCell = new Cell();
Table innerTable = new Table(new float[] { 100 });
innerTable.SetWidthPercent(100);
Cell innerTableCell = new Cell();
Paragraph paragraph = new Paragraph("ABCDEFGHIJKL").AddStyle(_fieldValueStyle);
innerTableCell.Add(paragraph);
innerTable.AddCell(innerTableCell);
outerTableCell.Add(innerTable);
outerTable.AddCell(outerTableCell);
innerTableCell.SetBorder(new SolidBorder(Color.RED, 2));
innerTableCell.SetBorderRight(new SolidBorder(Color.BLUE, 2));
outerTableCell.SetBorder(new SolidBorder(Color.GREEN, 2));
}
return outerTable;
}
Thanks mkl for spending your valuable time. I solved my issue with your idea of 'no inner tables'. This is not how to solve the issue of nested tables mentioned in the question but another way of achieving the result.
I've used "\n" in the paragraph to achieve what I want. Here is the output and the code.
private Table OuterTable()
{
var columns = GetTableColumnWidth(10);
var outerTable = new Table(columns, true);
outerTable.SetWidthPercent(100);
for (int index = 0; index < columns.Length; index++)
{
Cell outerTableCell = new Cell();
outerTableCell.Add(GetContent());
outerTable.AddCell(outerTableCell);
}
return outerTable;
}
private Paragraph GetContent()
{
int maxIndex = 3;
Paragraph paragraph = new Paragraph();
for (int index = 0; index < maxIndex; index++)
{
paragraph.Add(index + " - ABCDEFGHIJKL \n").AddStyle(_fieldValueStyle);
}
return paragraph;
}
I have a simple celltable with:
a)A timer for retrieving the Whole list of values from the server (via rpc). when the data comes from the server:
public void onSuccess(final Object result) {
myList ((List<myObject>) result);
setRowData(myList);
}
b)A AsyncDataProvider to refresh the current displayed page, where:
protected void onRangeChanged(HasData<myObject> display) {
final Range range = display.getVisibleRange();
int start = range.getStart();
int end = start + range.getLength();
List<myObject> dataInRange = myList.subList(start, end);
// Push the data back into the list.
setRowData(start, dataInRange);
}
This works fine, refreshing the table when new data arrives from the server ..BUT....It jumps to the first page of my displayed table regardless he current page (Page size=20).
It is like it is ignoring the start and dataInRange values of the onRangeChanged method
the sentence:
setRowData(myList);
Is firing properly the onRangeChanged event of the DataProvider, but some how the 'start' values get 0
Any tip?
Many thanks
The problem is that when you call setRowData(myList); you also change the row range.
See the documentation:
Set the complete list of values to display on one page.
Equivalent to calling setRowCount(int) with the length of the list of values, setVisibleRange(Range) from 0 to the size of the list of values, and setRowData(int, List) with a start of 0 and the specified list of values.
Literally, below is the implementation:
public final void setRowData(List<? extends T> values) {
setRowCount(values.size());
setVisibleRange(0, values.size());
setRowData(0, values);
}
Need to sort/order a list of data based on an undetermined number of columns (1 or more).
What i'm trying to do is loop through the desired columns and add an OrderBy or ThenBy based on their number to the query'd list, but i'm unsuccessful...
Done this, but it doesn't compile:
var query = GetAllItems(); //returns a IQueriable list of items
//for each selected column
for (int i = 0; i < param.Columns.Length; i++)
{
if (i == 0)
{
query = query.OrderBy(x => x.GetType().GetProperty(param.Columns[i].Name));
}
else
{
//ERROR: IQueriable does not contain a definition for "ThenBy" and no extension method "ThenBy"...
query = query.ThenBy(x => x.GetType().GetProperty(param.Columns[i].Data));
}
}
How can i resolve this issue? Or any alternative to accomplish this requirement?
SOLUTION: #Dave-Kidder's solution is well thought and resolves the compile errors i had. Just one problem, OrderBy only executes (actually sorts the results) after a ToList() cast. This is an issue because i can't convert a ToList back to an IOrderedQueryable.
So, after some research i came across a solution that resolve all my issues.
Microsoft assembly for the .Net 4.0 Dynamic language functionality: https://github.com/kahanu/System.Linq.Dynamic
using System.Linq.Dynamic; //need to install this package
Updated Code:
var query = GetAllItems(); //returns a IQueriable list of items
List<string> orderByColumnList = new List<string>(); //list of columns to sort
for (int i = 0; i < param.Columns.Length; i++)
{
string column = param.Columns[i].Name;
string direction = param.Columns[i].Dir;
//ex.: "columnA ASC"
string orderByColumn = column + " " + direction;
//add column to list
orderByColumnList.Add(orderBy);
}
//convert list to comma delimited string
string orderBy = String.Join(",", orderByColumnList.ToArray());
//sort by all columns, yay! :-D
query.OrderBy(orderBy).ToList();
The problem is that ThenBy is not defined on IQueryable, but on the IOrderedQueryable interface (which is what IQueryable.OrderBy returns). So you need to define a new variable for the IOrderedQueryable in order to do subsequent ThenBy calls. I changed the original code a bit to use System.Data.DataTable (to get a similar structure to your "param" object). The code also assumes that there is at least one column in the DataTable.
// using System.Data.DataTable to provide similar object structure as OP
DataTable param = new DataTable();
IQueryable<DataTable> query = new List<DataTable>().AsQueryable();
// OrderBy returns IOrderedQueryable<TSource>, which is the interface that defines
// "ThenBy" so we need to assign it to a different variable if we wish to make subsequent
// calls to ThenBy
var orderedQuery = query.OrderBy(x => x.GetType().GetProperty(param.Columns[0].ColumnName));
//for each other selected column
for (int i = 1; i < param.Columns.Count; i++)
{
orderedQuery = orderedQuery.ThenBy(x => x.GetType().GetProperty(param.Columns[i].ColumnName));
}
you should write ThenBy after OrderBy like this:
query = query
.OrderBy(t=> // your condition)
.ThenBy(t=> // next condition);
Grid g=new Grid(arg0.size(),5);
g.setBorderWidth(5);
g.setTitle("users data");
g.setVisible(true);
g.setCellSpacing(3);
g.setCellPadding(2);
g.setText(0,0, "empid");
g.setText(0,1, "first name");
g.setText(0,2, "last name");
g.setText(0,3, "gender");
g.setText(0,4, "studies");
System.err.println("list = "+arg0);
for(int i=0;i<arg0.size();i++)
{
User data= (User) arg0.get(i);
for(int row=1;row<arg0.size();row++)
{
for(int col=0;col<5;col++)
{
g.setText(row, col,String.valueOf(data.getEmpid()));
g.setText(row, col,data.getFirstname());
g.setText(row, col, data.getLastname());
g.setText(row, col,data.getGender());
g.setText(row, col, data.getStudies());
}
}
}
RootPanel.get().clear();
RootPanel.get().add(g);
arg0 is list of User pojo. in every column the last field is only adding but we have to add appropriate data into each column. mostly i did wrong in for loop please correct me and give response
To add the data correctly, have the for-loop like this:
// make the grid the size of the userlist + 1 for the header line
Grid g = new Grid(arg0.size() + 1, 5);
// rest of the Grid-definition
;
// per user in the user-list, add one row
for(int i=0;i<arg0.size();i++){
// retrieve the current user
User data= (User) arg0.get(i);
// add the data of that user in the correct column
// concerning the row: add in currentRow+1 to compensate for header row
g.setText(i+1, 0,String.valueOf(data.getEmpid()));
g.setText(i+1, 1,data.getFirstname());
g.setText(i+1, 2, data.getLastname());
g.setText(i+1, 3,data.getGender());
g.setText(i+1, 4, data.getStudies());
}
However, another way would be using a CellTable. It is much more flexible than your Grid, you can easily add/remove rows, and it's much easier to e.g. change the order of the columns, should that be necessary some time in the future. Additionally, it provides interfaces for column sorting should that be necessary. If you're interested in seeing a CellTable example, let me know so that I can update this post.
I'm using getElementsByTagName to return all the select lists on a page - is it possible to then filter these based upon an option value, ie of the first or second item in the list?
The reason is that for reasons I won't go into here there are a block of select lists with number values (1,2,3,4,5 etc) and others which have text values (Blue and Black, Red and Black etc) and I only want the scripting I have to run on the ones with numerical values. I can't add a class to them which would more easily let me do this however I can be certain that the first option value in the list will be "1".
Therefore is there a way to filter the returned list of selects on the page by only those whose first option value is "1"?
I am pretty sure that there is a better solution, but for the moment you can try something like:
var allSelect = document.getElementsByTagName("select");
var result = filterBy(allSelect, 0/*0 == The first option*/, "1"/* 1 == the value of the first option*/);
function filterBy(allSelect, index, theValue) {
var result = [];
for (var i = 0; i < allSelect.length; i++) {
if(allSelect[i].options[index].value == theValue ) {
result.push(allSelect[i]);
}
}
return result;
}
I managed to get this working by wrapping a simple IF statement around the action to be performed (in this case, disabling options) as follows:
inputs = document.getElementsByTagName('select');
for (i = 0; i < inputs.length; i++) {
if (inputs[i].options[1].text == 1) {
// perform action required
}
}
No doubt there is a slicker or more economic way to do this but the main thing is it works for me.