I would like to specify a default sorting column of a tapestry grid.
I understand that I could sort the records in my getter on the server side, however this seems like unnecessary effort, since grid is perfectly capable of sorting on it's own.
So my grid definition looks like:
<t:grid source="queues" inPlace="true"> </t:grid>
The getQueues returns collection of objects like:
public class Queue {
public String getName();
public float getOccupancy();
}
I would like to make the grid sorted by 'occupancy' attribute in descendent order by default.
From the Tapestry documentation I see, that Grid has attribute sortModel which seems like the right option. However I can't find good explanation of what are the right values to set it to.
You could probably write a mixin to set the initial sort
#MixinAfter
public class DefaultSort {
public enum Order { ASCENDING, DESCENDING };
#Parameter(required=true, defaultPrefix="literal")
private String sort;
#Parameter(defaultPrefix="literal", value="literal:ascending")
private Order order;
#InjectContainer
private Grid grid;
#SetupRender
void setupRender() {
GridSortModel sortModel = grid.getSortModel();
if (sortModel.getSortConstraints().isEmpty()) {
sortModel.updateSort(sort);
if (order == Order.DESCENDING) {
// updateSort a second time for DESCENDING
sortModel.updateSort(sort);
}
}
}
}
<t:grid source="queues" inPlace="true" t:mixins="defaultsort" sort="occupancy" />
Related
I red a lot about sorting a CellTable. I also went trough the ColumnSorting with AsyncDataProvider. But my CellTable does not sort.
Here is my code:
public class EventTable extends CellTable<Event> {
public EventTable() {
EventsDataProvider dataProvider = new EventsDataProvider(this);
dataProvider.addDataDisplay(this);
SimplePager.Resources pagerResources = GWT.create(SimplePager.Resources.class);
SimplePager pager = new SimplePager(TextLocation.CENTER, pagerResources, false, 5, true);
pager.setDisplay(this);
[...]
TextColumn<Event> nameCol = new TextColumn<Event>() {
#Override
public String getValue(Event event) {
return event.getName();
}
};
nameCol.setSortable(true);
AsyncHandler columnSortHandler = new AsyncHandler(this);
addColumnSortHandler(columnSortHandler);
addColumn(nameCol, "Name");
getColumnSortList().push(endCol);
}
}
public class EventsDataProvider extends AsyncDataProvider<Event> {
private final EventTable eventTable;
public EventsDataProvider(EventTable eventTable) {
this.eventTable = eventTable;
}
#Override
protected void onRangeChanged(HasData<Event> display) {
int start = display.getVisibleRange().getStart();
int length = display.getVisibleRange().getLength();
// check false values
if (start < 0 || length < 0) return;
// check Cache before making a rpc
if (pageCached(start, length)) return;
// get Events async
getEvents(start, length);
}
}
I do now know, if all the methods are need here. If so, I will add them. But in short:
pageCached calls a method in my PageCache Class which holds a map and a list. Before making a rpc call, the cache is checked if the events where already taken and then displayed.
getEvents just makes an rpc call via asynccallback which updates the rowdata via updateRowData() on success.
My Table is displayed fast with currently around 500 entries (could be more, depends on the customer). No missing data and the paging works fine.
I just cannot get the sorting work. As far as I know, AsyncHandler will fire a setVisibleRangeAndClearData() and then an onRangeChanged(). onRangeChanged is never fired. As for the setVisibleRangeAndClearData() I do not know. But the sortindicator (arrow next to the columnname) does change on every click.
I do not want to let the server sort the list. I have my own Comparators. It is enough, if the current visible page of the table is sorted. I do now want to sort the whole list.
Edit:
I changed following code in the EventTable constructor:
public EventTable() {
[...]
addColumnSortHandler(new ColumnSortEvent.AsyncHandler(this) {
public void onColumnSort(ColumnSortEvent event) {
super.onColumnSort(event);
MyTextColumn<Event> myTextColumn;
if (event.getColumn() instanceof MyTextColumn) {
// Compiler Warning here: Safetytype unchecked cast
myTextColumn = (MyTextColumn<Event>) event.getColumn();
MyLogger.log(this.getClass().getName(), "asc " + event.isSortAscending() + " " + myTextColumn.getName(), Level.INFO);
}
List<Event> list = dataProvider.getCurrentEventList();
if (list == null) return;
if (event.isSortAscending()) Collections.sort(list, EventsComparator.getComparator(EventsComparator.NAME_SORT));
else Collections.sort(list, EventsComparator.descending(EventsComparator.getComparator(EventsComparator.NAME_SORT)));
}
});
addColumn(nameCol, "Name");
getColumnSortList().push(endCol);
}
I had to write my own TextColumn to determine the Name of the column. Otherwise how should I know, which column was clicked? The page gets sorted now but I have to click twice on the column. After then, the sorting is done with every click but in the wrong order.
This solution does need polishing and it seems kinda hacky to me. Any better ideas?
The tutorial, that you linked to, states:
This sorting code is here so the example works. In practice, you would
sort on the server.
Async provider is used to display data that is too big to be loaded in a single call. When a user clicks on any column to sort it, there is simply not enough objects on the client side to display "first 20 evens by name" or whatever sorting was applied. You have to go back to your server and request these first 20 events sorted by name in ascending order. And when a user reverses sorting, you have to go to the server again to get first 20 events sorted by name in a descending order, etc.
If you can load all data in a single call, then you can use regular DataProvider, and all sorting can happen on the client side.
EDIT:
The problem in the posted code was in the constructor of EventsDataProvider. Now it calls onRangeChanged, and the app can load a new sorted list of events from the server.
In my application I have a list of keys (strings), where the user can select one of them. In the user-interface, the keys will be output according to the current locale:
<h:selectOneMenu value="#{bean.selectedKey}">
<f:selectItems value="#{bean.allKeys}" var="_key" itemLabel="#{msgs[_key]}" />
</h:selectOneMenu>
My setup uses a standard resource-bundle configured in faces-config.xml as explained in this answer by BalusC. msgs in the example above is the name of the resource-bundle variable.
What I want now, is the items from the selectOneMenu to be sorted in alphabetic order. Of course the order depends on the used locale. The problem is, I can't/won't do the sorting in the backing-bean, as I don't know how the JSF-page will output the keys.
This problem seems quite generic to me, so I'm wondering what the best practice is to solve this kind of problem.
(Of course the problem is not only applicable to selectOneMenu. Any list/collection that will be output in the user-interface suffers from the same problem.)
You've basically 2 options.
Sort in client side with a little help of JS. I'll for simplicity assume that you're using jQuery.
<h:selectOneMenu ... styleClass="ordered">
$("select.ordered").each(function(index, select) {
var $select = $(select);
$select.find("option").sort(function(left, right) {
return left.text == right.text ? 0 : left.text < right.text ? -1 : 1;
}).appendTo($select);
if (!$select.find("option[selected]").length) {
select.options.selectedIndex = 0;
}
});
Sort in server side wherein you create List<SelectItem> and grab #{msgs} via injection. Assuming that you're using CDI and thus can't use #ManagedProperty("#{msgs}"), you'd need to create a producer for that first. I'll for simplicity assume that you're using OmniFaces (which is also confirmed based on your question history).
public class BundleProducer {
#Produces
public PropertyResourceBundle getBundle() {
return Faces.evaluateExpressionGet("#{msgs}");
}
}
Then you can make use of it as follows in the backing bean associated with <f:selectItems value>:
private static final Comparator<SelectItem> SORT_SELECTITEM_BY_LABEL = new Comparator<SelectItem>() {
#Override
public int compare(SelectItem left, SelectItem right) {
return left.getLabel().compareTo(right.getLabel());
}
};
private List<SelectItem> allKeys;
#Inject
private PropertyResourceBundle msgs;
#PostConstruct
public void init() {
allKeys = new ArrayList<>();
for (String key : keys) {
allKeys.add(new SelectItem(key, msgs.getString(key)));
}
Collections.sort(allKeys, SORT_SELECTITEM_BY_LABEL);
}
And reference it directly without var as follows:
<f:selectItems value="#{bean.allKeys}" />
I want to do the same as explained here:
Sorting a list using Lambda/Linq to objects
that is:
public enum SortDirection { Ascending, Descending }
public void Sort<TKey>(ref List<Employee> list,
Func<Employee, TKey> sorter, SortDirection direction)
{
if (direction == SortDirection.Ascending)
list = list.OrderBy(sorter);
else
list = list.OrderByDescending(sorter);
}
to call it he said to do:
Sort(ref employees, e => e.DOB, SortDirection.Descending);
but I do not understand what TKey is refering to and as I can see in the call it is missed the generic TKey.
Could you explain me what is TKey and how to use it?
I suppose I can use another name for the method, it is not necessary to be Sort, right?
thanks!
You sort by the key which is of type TKey and must implement IComparable<TKey>. For instance:
// key: Firstname
// TKey: string (which is IComparable<String>
list.OrderBy(person => person.Firstname);
The above code sorts by firstname, which is what you define using the sorter. And yes, you can give your method any name you like. It does not have to be named Sort.
Improvement Suggestion (indirectly related to the question)
instead of changing list and passing it as a reference I'd suggest you to consider the following implementation:
public IOrderedEnumerable<Employee> Sort<TKey>(IEnumerable<Employee> list, Func<Employee, TKey> sorter, SortDirection direction);
{
IOrderedEnumerable<Employee> result;
if (direction == SortDirection.Ascending)
result = list.OrderBy(sorter);
else
result = list.OrderByDescending(sorter);
return result;
}
You could then return a new ordered enumerable of Employee objects instead of changing the old one and use any enumerable instead of List object only. This gives you more flexibility and is closer to the LINQ implementation which people tend to be used to.
I am trying to find best way to compare Enums in Java 6.
Say, I have an ENUM of Ticket Types which can be associated with a Traveler. If there is a list of travelers, I would like to know the traveler with the highest class of travel.
I can iterate thru the list of travelers, create a Set of unique TicketTypes, Convert to List, Sort them, Return the last element as the highest. I would like to know if there is a better way to do this?
public enum TicketType {
ECONOMY_NON_REF(1,"Economy Class, Non-Refundable"),
ECONOMY_REF(2,"Economy Full Fare Refundable"),
BUSINESS(3,"Business Class"),
FIRST_CLASS(4,"First Class, Top of the world");
private String code;
private String description;
}
public class Traveler {
private TicketType ticketType;
public Traveler(TicketType ticketType) {
this.ticketType = ticketType;
}
}
#Test
public testCompareEnums{
List<Traveler> travelersGroup1 = new ArrayList<Travelers>();
travelersGroup1.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup1.add(new Traveler(TicketType.BUSINESS));
travelersGroup1.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup1.add(new Traveler(TicketType.ECONOMY_NON_REF));
//What is the best way to find the highest class passenger in travelersGroup1.
assertEquals(TicketType.BUSINESS, getHighestClassTravler(travelersGroup1));
List<Traveler> travelersGroup2 = new ArrayList<Travelers>();
travelersGroup2.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup2.add(new Traveler(TicketType.ECONOMY_NON_REF));
travelersGroup2.add(new Traveler(TicketType.ECONOMY_REF));
travelersGroup2.add(new Traveler(TicketType.ECONOMY_NON_REF));
assertEquals(TicketType.ECONOMY_REF, getHighestClassTravler(travelersGroup2));
}
private CredentialType getHighestClassTraveler(List travelers){
Set uniqueTicketTypeSet = new HashSet();
for (Traveler t: travelers) {
uniqueTicketTypeSet.add(t.getTicketType());
}
List<TicketType> uniqueTicketTypes = new ArrayList<TicketType>(uniqueTicketTypeSet);
Collections.sort(uniqueTicketTypes);
return uniqueTicketTypes.get(uniqueTicketTypes.size()-1);
}
There's a lot of problems with the code that you posted (it won't compile without fixing a lot of errors), but the easiest way is to make Traveler implement the Comparable interface, like so:
public int compareTo(Traveler other) {
return this.getTicketType().compareTo(other.getTicketType());
}
Then to find the the Traveler with the highest TicketType, you can simply do:
Collections.max(travelers);
I'd like to create an IList<Child> that maintains its Child objects in a default/implicit sort order at all times (i.e. regardless of additions/removals to the underlying list).
What I'm specifically trying to avoid is the need for all consumers of said IList<Child> to explicitly invoke IEnumerable<T>.OrderBy() every time they want to enumerate it. Apart from violating DRY, such an approach would also break encapsulation as consumers would have to know that my list is even sorted, which is really none of their business :)
The solution that seemed most logical/efficient was to expose IList<Child> as IEnumerable<Child> (to prevent List mutations) and add explicit Add/Remove methods to the containing Parent. This way, I can intercept changes to the List that necessitate a re-sort, and apply one via Linq:
public class Child {
public string StringProperty;
public int IntProperty;
}
public class Parent{
private IList<Child> _children = new List<Child>();
public IEnumerable<Child> Children{
get
{
return _children;
}
}
private void ReSortChildren(){
_children = new List<Child>(child.OrderBy(c=>c.StringProperty));
}
public void AddChild(Child c){
_children.Add();
ReSortChildren()
}
public void RemoveChild(Child c){
_children.Remove(c);
ReSortChildren()
}
}
Still, this approach doesn't intercept changes made to the underlying Child.StringProperty (which in this case is the property driving the sort). There must be a more elegant solution to such a basic problem, but I haven't been able to find one.
EDIT:
I wasn't clear in that I would preferable a LINQ compatible solution. I'd rather not resort to using .NET 2.0 constructs (i.e. SortedList)
What about using a SortedList<>?
One way you could go about it is to have Child publish an event OnStringPropertyChanged which passes along the previous value of StringProperty. Then create a derivation of SortedList that overrides the Add method to hookup a handler to that event. Whenever the event fires, remove the item from the list and re-add it with the new value of StringProperty. If you can't change Child, then I would make a proxy class that either derives from or wraps Child to implement the event.
If you don't want to do that, I would still use a SortedList, but internally manage the above sorting logic anytime the StringProperty needs to be changed. To be DRY, it's preferable to route all updates to StringProperty through a common method that correctly manages the sorting, rather than accessing the list directly from various places within the class and duplicating the sort management logic.
I would also caution against allowing the controller to pass in a reference to Child, which allows him to manipulate StringProperty after it's added to the list.
public class Parent{
private SortedList<string, Child> _children = new SortedList<string, Child>();
public ReadOnlyCollection<Child> Children{
get { return new ReadOnlyCollection<Child>(_children.Values); }
}
public void AddChild(string stringProperty, int data, Salamandar sal){
_children.Add(stringProperty, new Child(stringProperty, data, sal));
}
public void RemoveChild(string stringProperty){
_children.Remove(stringProperty);
}
private void UpdateChildStringProperty(Child c, string newStringProperty) {
if (c == null) throw new ArgumentNullException("c");
RemoveChild(c);
c.StringProperty = newStringProperty;
AddChild(c);
}
public void CheckSalamandar(string s) {
if (_children.ContainsKey(s))
var c = _children[s];
if (c.Salamandar.IsActive) {
// update StringProperty through our method
UpdateChildStringProperty(c, c.StringProperty.Reverse());
// update other properties directly
c.Number++;
}
}
}
I think that if you derive from KeyedCollection, you'll get what you need. That is only based on reading the documentation, though.
EDIT:
If this works, it won't be easy, unfortunately. Neither the underlying lookup dictionary nor the underlying List in this guy is sorted, nor are they exposed enough such that you'd be able to replace them. It might, however, provide a pattern for you to follow in your own implementation.