Vaadin: Daisy chain of filters? - java

I have this particular problem now - I have a grid that I am trying to have the data filtered through multiple filters. For that, I am using textboxes that serve as input fields for my filtering criterion.
My grid has three columns (First Name, Last Name, Address) and I would like to be able to chain the filtering operations one after the other. All of the values are taken from a MySQL database.
Essentially the filter process should go like this:
FirstName ^ LastName ^ Address
For example, grid with three columns:
And in the filter for First Name column, I input the variables Aa, which would result in the table looking like this:
However, if I decided input D into the Last Name filter it returns results like this (ignores the modifications by the first filter):
Instead of the expected result which would look like this:
The way I am filtering through the grid is like this:
firstNameFilter.addValueChangeListener( e->
{
Notification.show(e.getValue());
ldp.setFilter(desc ->
{
return StringUtils.containsIgnoreCase(desc.getFName(), firstNameFilter.getValue());
});
});
firstNameFilter.setValueChangeMode(ValueChangeMode.EAGER);
What would be the best way to filter through multiple columns whilst taking into consideration previous filter actions?

listDataProvider.setFilter(...) will overwrite any existing filter.
I have written an answer about this very topic, with a complete example code ready for copy paste, and screenshots showing that the multiple filters work as expected.
The most important takeaway from it is this:
Every time that any filter value changes, I reset the current filter using setFilter. But within that new Filter, I will check the values of ALL filter fields, and not only the value of the field whose value just changed. In other words, I always have only one single filter active, but that filter accounts for all defined filter-values.
Here is how it could look with your code:
firstNameFilter.addValueChangeListener( e-> this.onFilterChange());
lastNameFilter.addValueChangeListener( e-> this.onFilterChange());
addressFilter.addValueChangeListener( e-> this.onFilterChange());
// sidenote: all filter fields need ValueChangeMode.EAGER to work this way
private void onFilterChange(){
ldp.setFilter(desc -> {
boolean fNameMatch = true;
boolean lNameMatch = true;
boolean addressMatch = true;
if(!firstNameFilter.isEmpty()){
fNameMatch = StringUtils.containsIgnoreCase(desc.getFName(), firstNameFilter.getValue());
}
if(!lastNameFilter.isEmpty()){
lNameMatch = StringUtils.containsIgnoreCase(desc.getLName(), lastNameFilter.getValue());
}
if(!addressFilter.isEmpty()){
addressMatch = StringUtils.containsIgnoreCase(desc.getAddress(), addressFilter.getValue());
}
return fNameMatch && lNameMatch && addressMatch;
});
});

Related

Validating an object with a long list of predicates

I have an object, Bill, with a number of fields. In the method below, I get the bill with a function. I want to validate it with a list of Predicate<Bill>, which are paired with the appropriate error message to be applied if the predicate test fails. How can I accumulate the error messages given a list of tests, given that I can have more than eight conditions, and therefore won't be able to use Validation.combine?
default Validation<Seq<String>, Long> validate(
Long id,
Long, Bill> getBill,
List<Pair<Predicate<Bill>,String>> tests){
Bill bill = getBill.apply(id);
//I want to do the same thing
//but using the list I passed in,
//without the limitation of eight validations.
return Validation.combine(
validateBill(bill, Pair.of(hasDateInsurerReceivedBill, "Date Insurer Received Bill absent")),
validateBill(bill, Pair.of(EventValidation.hasEmployeeIdNumber, "Employee ID Number absent"))
).ap((x, y) -> id);
}
default Validation<String,Long> validateBill(
Bill bill, Pair<Predicate<Bill>, String> condition)
{
return condition.getFirst().test(bill) ?
Validation.valid(bill.getIntId())
: Validation.invalid(condition.getSecond());
}
I'm brand new to this library and I'm not terribly familiar with functional programming yet, so please use examples and the simplest terminology possible in any explanations.
I would do a nested combine and then flatten the results.
In our project we always have Seq<ValidationError> on the left side of a Validation, you don't have to but it is good to understand the code I'll show you.
With the first 8 Validations you return a new Validation in the .ap
When you return a Validation inside .ap you will end up with something like this:
Validation<Seq<ValidationError>, Validation<Seq<ValidationError>, String>> x = ...
The needs to be flattened with the following piece of code:
Validation
.combine(step1, step2, step3, step4, step5, step6, step7, step8)
.ap((a, b, c, d, e, f ,g, h) -> {
// do important stuff and
return Validation......
})
.mapError(Util::flattenErrors)
.fold(Validation::invalid, Function.identity());
The Util class:
public static Seq<ValidationError> flattenErrors(final Seq<Seq<ValidationError>> nested) {
return nested
.flatMap(Function.identity())
.distinct(); //optional duplicate filtering
}
With this new validation you can do the same trick again (you can add 7 new validations every time or create a few and do another combine, depends a bit on the number of validations you have).

Assigning New Object to a Generic Array Index

I'm POSITIVE that my title for this topic is not appropriate. Let me explain. The purpose of this is to duplicate a "Profile" application, where I have a profile and so would you. We both have our own followers and in this example, we both follow each other. What this method is needed to return is a cross reference based on whom you follow that I do not. I need this method to return to me a recommended Profile object that I do not already have in my array. Right now I'm having a difficult time with one line of code within a particular method.
One of my classes is a Set class that implements a SetInterface (provided by my professor) and also my Profile class that implements a ProfileInterface which was also provided. In my code for the Profile class, I have the following object: private Set<ProfileInterface> followBag = new Set<ProfileInterface>(); which utilizes the Array bag methods from my Set class with the ProfileInterface methods I've made.
Here is the method (not complete but can't move further without my problem being explained):
public ProfileInterface recommend(){
Set<ProfileInterface> recommended;
ProfileInterface thisProfile = new Profile();
for(int index = 0; index < followBag.getCurrentSize(); index++){
Set<ProfileInterface> follows = followBag[index].toArray();
for(int followedFollowers = 0; followedFollowers < follows.getCurrentSize(); followedFollowers++) {
if()
//if Profile's do not match, set recommended == the Profile
}
}
return recommended;
}
The purpose of this method is to parse through an array (Profile as this example) and then take each of those sub-Profiles and do a similar action. The reason for this much like "Twitter", "Facebook", or "LinkedIn"; where each Profile has followers. This method is meant to look through the highest Profiles follows and see if those subProfiles have any followers that aren't being followed by the highest one. This method is then meant to return that Profile as a recommended one to be followed. This is my first dealing with Array Bag data structures, as well as with generics. Through "IntelliJ", I'm receiving errors with the line Set<ProfileInterface> follows = followBag[index].toArray();. Let me explain the reason for this line. What I'm trying to do is take "my" profile (in this example), and see who I'm following. For each followed profile (or followBag[index]) I wish to see if followBag[index][index] == followBag[index] and continue to parse the array to see if it matches. But, due to my confusion with generics and array bag data structures, I'm having major difficulties figuring this out.
I'd like to do the following:
//for all of my followers
//look at a particular followed profile
//look at all of that profile's followers
//if they match one of my followers, do nothing
//else
//if they don't match, recommend that profile
//return that profile or null
My problem is that I do not know how to appropriately create an object of a Profile type that will allow me to return this object
(in my method above, the line Set<ProfileInterface> follows = followBag[index].toArray();)
I'm trying to make an index of my Profile set to an object that can later be compared where my difficulties are. I'd really appreciate any insight into how this should be done.
Much appreciated for all help and Cheers!
When you do:
Set<ProfileInterface> follows = followBag[index].toArray();
you're trying to use Set as Array. But you can't.
Java will not allow, because Set and Array are different classes, and Set does not support [] syntax.
That is why you get error. For usefollowBag as Array you have to convert it:
ProfileInterface[] profileArray = followBag.toArray(new ProfileInterface[followBag.size()]);
for(int i=0; i<profileArray.length; i++){
ProfileInterface profile = profileArray[i];
//do what you would like to do with array item
}
I believe, in your case, you don't need assign Set object to generic Array at all. Because you can enumerate Set as is.
public class Profile {
private Set<ProfileInterface> followBag = new HashSet<Profile>();
...
public Set<ProfileInterface> recommended(){
Set<ProfileInterface> recommendSet = new HashSet<ProfileInterface>();
for(Profile follower : followBag){
for(Profile subfollower : follower.followBag){
if(!this.followBag.contains(subfollower)){
recommendSet.add(subfollower);
}
}
}
return recommendSet;
}
}
I also added possibility of returning list of recommended profiles, because there is may be several.

How to get multiple selected rows in a table or indexedcontainer?

I have a Table whose DataSource is set to a IndexedContainer. I also have multiple selection enabled on my Table. The Question is, how do I get all the selected values.. as an array perhaps?
My IndexedContainer:
private void populateAnalyteTable () {
Analyte[] analytes = Analyte.getAnalytes();
for (Analyte analyte : analytes) {
Object id = ic_analytes.addItem();
ic_analytes.getContainerProperty(id, "ID").setValue(analyte.getId());
ic_analytes.getContainerProperty(id, "Analyte Name").setValue(analyte.getAnalyteName());
}
// Bind indexed container to table
tbl_analytes.setContainerDataSource(ic_analytes);
}
What I'm eventually trying to get is an array of Analyte objects
Why do you want to use IndexContainer? Why don't you use BeanItemCotainer?
Please find the snippet of code below
table.setMultiSelect(true);
BeanItemContainer<Analyte> container = new BeanItemContainer<Analyte>(Analyte.class);
container.addAll(Arrays.asList(Analyte.getAnalytes()));
table.setContainerDatasource(container);
// Add some Properties of Analyte class that you want to be shown to user
table.setVisibleColumns(new Object[]{"ID","Analyte Name"});
//User selects Multiple Values, mind you this is an Unmodifiable Collection
Set<Analyte> selectedValues = (Set<Analyte>)table.getValue();
Please let me know in case it doesn't solve the issue
The vaadin objects supporting MultiSelect all return a set of the selected items.
https://www.vaadin.com/api/com/vaadin/ui/AbstractSelect.html#getValue%28%29
The drawback of this, if you need the selected items in "real" order (as displayed onscreen)
you will then have to find them from the Set to the Container
Just add your object as the Item-ID, like luuksen already propesed. Just change the initialisation of yout IndexedContainer to:
for (Analyte analyte : analytes) {
Object id = ic_analytes.addItem(analyte);
ic_analytes.getContainerProperty(id, "ID").setValue(analyte.getId());
ic_analytes.getContainerProperty(id, "Analyte Name").setValue(analyte.getAnalyteName());
}
table.getValue() is what you are looking for.
This method gives you an Object (if table is single select) or a Set<Object> (if multiselect) of the ID(s) of selected item(s). Runtime type depends on runtime id type, but if you do not need the value you can go around with Object .
If you are looking for Analytes as an array you can do
#SuppressWarnings("unchecked")
Set<Object> selectedIds = (Set<Object>) tbl_analytes.getValue();
List<Analyte> listAnalytes = new ArrayList<Analyte>();
for (Object id : selectedIds) {
listAnalytes.get(tbl_analytes.getItem(id));
}
listAnalytes.toArray();
Note that this approach works with every standard container you may use in Vaadin.
Regards!
EDIT: actually what .getValue() returns depends on the used container. In most of the cases it's the ID.

How to iterate and add each "many" in a one-to-many class

I have two classes. The OrderSlip class has a one-to-many relationship with orderedItemDescription.
class OrderSlip {
String employeeID
int serving
int tableNumber
static hasMany = [orderedItemDescription: OrderedItemDescription]
}
class OrderedItemDescription {
MenuItem menuItem
MenuItemProgressStatus progress//progress
String descriptionOfOrder
int quantity = 1
static belongsTo = OrderSlip
}
Now my problem is how do i iterate orderedItemDescription so that when i update my orderSlip i can add many orderedItemDescriptions along with its properties.
def updateOrderSlip(Long id) {
User currentUser = springSecurityService.currentUser
def orderSlipInstance = Table.get(id)
//other codes for orderedItemDescription here
orderSlipInstance.employeeID = currentUser.username
orderSlipInstance.serving= Integer.parseInt(params.serving)
orderSlipInstance.tableNumber= params.tableNumber
render(action:'server')
}
Im doing something like this in my gsp. im only adding data to the DOM with the add buttons. Then for the send order im hoping i can update it like the problem since im also adding many g:hiddenField for each orderedItemDescription in my summary
You should be persisting each new instance OrderedItemDescription somehow.
You can store it immediately in the DB upon click on add-button with the status flag set to incomplete. When you save the whole order, you must change the incomplete to complete.
Another option would be to keep the items in the http session. Upon send order you iterate through the in-session items and persist them all along with the order instance.
Both ways have advantages and drawbacks, but they both are useful.

How to remove previous attributeModifier when new attributeModifier is added?

I have two columns which are orderbyborder links. When i click one column i changed the color of column by adding attributeModifier in the following way
add(new AttributeModifier("style", true, new Model<String>("background-color:#80b6ed;")));
This works fine. But when i click on second column, the first column remains the changed color. But I expect only the column which i click should hold this attributeModifier!
You shouldn't change the modifier.
The trick is to have your model return the correct value. So instead of using new Model<String>("background-color:#80b6ed;"), which always returns the same constant value, you'd have something like:
new Model<String>() {
#Override
public String getObject() {
if( columnName.equals( selectedColumn ) { //or something along these lines, to check if the current column is the selected one
return "background-color:#80b6ed;";
}
return "background-color:white;";
}
}
And of course this also means you can add an attribute modifier to every column when you create them and don't have to worry about them later on.
Another way to achieve what you want is to add a css class to the selected line via Javascript (removing the class from old one).

Categories