Question

Can I easily put the entry values of a filtered view into a hashmap?

I have a repeat control, bound to a view, with a dynamic filter. The user can change the filter by several djFilteringSelect controls, corresponding to the view columns.

Depending on the selection in the 1st djFilteringSelect, the selection in the next djFilteringSelects should be limited to the possible entries ("similar to the data filter in excel"). At the moment I do this with separate @dbcolumn/@dblookup methods for the djFilteringSelects, but I think it is much better and easier to just fill the view entries values into a hashmap and show the hashmap values in the djFilteringSelect.

I found few threads here with repeat controls and hashmaps, but these examples also build the doc collection separatly, which I wish to avoid.

Thanks for any help, Uwe

Was it helpful?

Solution

There's a reason why all these examples build their document collections separately. Instead of "the view is in the UI", so I must use it, you might have an easier time to build a bean that serves as the source for your repeat control. A bean data source or a managed bean. This will allow for a use case, where you show 2 filter results (e.g England/London and France/Lyon) in one display, something a filtered view can't do.

Update

If you have a lot of reader/author fields, you want to have a view categorized by them, to populate your "backing bean" - there is lot of performance to gain. Holding a few hundred items in memory isn't a big deal.

There are 2 trains of though: make it generic, so every line in the view ends up as a Collection item (Array, List, Map etc.) or to build dedicated line items with clear names. This trains collide quite often, let me take you to the dedicated train for a moment. :-) So your classes (you need 2) would look like this:

    package test;

    import java.io.Serializable;
    import java.util.Vector;

    import lotus.domino.Database;
    import lotus.domino.Document;
    import lotus.domino.NotesException;
    import lotus.domino.ViewEntry;

    public class Fruit implements Serializable {

        private static final long   serialVersionUID    = 1L;

        private String              name;
        private String              color;
        private String              shape;
        private String              taste;
        private String              unid                = null;

        public Fruit() {
            // default constructor, might not be needed
        }

        // To make it easy from a view
        public Fruit(final ViewEntry ve) {
            try {
                @SuppressWarnings("rawtypes")
                Vector v = ve.getColumnValues();
                // 0 would be the user/group/role
                this.setName(v.get(1).toString());
                this.setColor(v.get(2).toString());
                this.setShape(v.get(3).toString());
                this.setTaste(v.get(4).toString());
                this.unid = ve.getUniversalID();
            } catch (NotesException e) {
                e.printStackTrace();
            }

        }

        public void save(Database db) throws NotesException {
            Document doc;
            if (this.unid == null) {
                doc = db.createDocument();
            } else {
                doc = db.getDocumentByUNID(this.unid);
            }
            doc.replaceItemValue("Color", this.getColor());
            // more here
            doc.save();
        }

        public final String getName() {
            return this.name;
        }

        public final void setName(String name) {
            this.name = name;
        }

        public final String getColor() {
            return this.color;
        }

        public final void setColor(String color) {
            this.color = color;
        }

        public final String getShape() {
            return this.shape;
        }

        public final void setShape(String shape) {
            this.shape = shape;
        }

        public final String getTaste() {
            return this.taste;
        }

        public final void setTaste(String taste) {
            this.taste = taste;
        }
    }

That's to hold a line item (using my favourite fruits example). In your code the repeat control variable (or the data table variable) - instead of the view control, would hold one Fruit instance coming from fruitController.getSelectedFruits() (which you can use in EL as fruitController.selectedFruits), so you can bind your columns using varName.color, varname.shape

The class around it looks roughly like:

    package test;

    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Set;
    import java.util.TreeSet;

    import lotus.domino.Database;
    import lotus.domino.NotesException;
    import lotus.domino.Session;
    import lotus.domino.View;
    import lotus.domino.ViewEntry;
    import lotus.domino.ViewEntryCollection;

    public class FruitController implements Serializable {

        private static final long       serialVersionUID    = 1L;
        private static final String     FRUIT_VIEW          = "(FruitsByUser)";

        private final Collection<Fruit> allFruits           = new ArrayList<Fruit>();
        private final Set<String>       colors              = new TreeSet<String>();
        private final Set<String>       shape               = new TreeSet<String>();
        private final Set<String>       taste               = new TreeSet<String>();

        private String                  colorFilter         = null;
        private String                  tasteFilter         = null;
        private String                  shapeFilter         = null;

        // if you use this controller, you only can use an object data source!
        // for a bean you would need an empty controller
        public FruitController(final Session s, final Database db) {
            this.populateData(s, db);
        }

        public final String getColorFilter() {
            return this.colorFilter;
        }

        public final String[] getColors() {
            return (String[]) this.colors.toArray();
        }

        public Collection<Fruit> getSelectedFruits() {
            Collection<Fruit> result = new ArrayList<Fruit>();

            for (Fruit f : this.allFruits) {
                if (this.matchesFilter(f)) {
                    result.add(f);
                }
            }

            return result;
        }

        public final String[] getShape() {
            return (String[]) this.shape.toArray();
        }

        public final String getShapeFilter() {
            return this.shapeFilter;
        }

        public final String[] getTaste() {
            return (String[]) this.taste.toArray();
        }

        public final String getTasteFilter() {
            return this.tasteFilter;
        }

        public void resetFilters() {
            this.setColorFilter(null);
            this.setShapeFilter(null);
            this.setTasteFilter(null);
        }

        public final void setColorFilter(String colorFilter) {
            this.colorFilter = colorFilter;
        }

        public final void setShapeFilter(String shapeFilter) {
            this.shapeFilter = shapeFilter;
        }

        public final void setTasteFilter(String tasteFilter) {
            this.tasteFilter = tasteFilter;
        }

        private boolean matchesFilter(Fruit f) {
            boolean result = true;

            result = ((result == false) ? false : ((this.colorFilter == null || "".equals(this.colorFilter.trim())) ? true
                    : (this.colorFilter.equals(f.getColor()))));
            result = ((result == false) ? false : ((this.tasteFilter == null || "".equals(this.tasteFilter.trim())) ? true
                    : (this.tasteFilter.equals(f.getTaste()))));
            result = ((result == false) ? false : ((this.shapeFilter == null || "".equals(this.shapeFilter.trim())) ? true
                    : (this.shapeFilter.equals(f.getShape()))));

            return result;
        }

        private void populateData(final Session s, final Database db) {
            try {
                final View v = db.getView(FRUIT_VIEW);
                // You might need to loop a little here to get all the values
                final ViewEntryCollection vec = v.getAllEntriesByKey(s.getUserName());
                ViewEntry ve = vec.getFirstEntry();

                while (ve != null) {
                    ViewEntry nextVe = vec.getNextEntry(ve);
                    Fruit f = new Fruit(ve);
                    this.updateSelectors(f);
                    this.allFruits.add(f);
                    ve = nextVe;
                    nextVe.recycle();
                }

                vec.recycle();
                v.recycle();
            } catch (NotesException e) {
                // TODO Stacktrace is no error handling
                e.printStackTrace();
            }

        }

        private void updateSelectors(Fruit f) {
            this.colors.add(f.getColor());
            this.shape.add(f.getShape());
            this.taste.add(f.getTaste());
        }

    }

Of course you can make that more sophisticated by filtering the selection values based on the other selections (e.g. after picking a color only offer the shapes that are available in that color). Using the class as object datasource (e.g. fruitController) should be easy. You can bind your dropdowns to fruitController.colorFilter etc. in EL and define the selections in EL as fruitController.Colors.

Update 2

The data source should be an object data source, like this one:

   <xp:this.data>
     <xe:objectData var="fruitController" ignoreRequestParams="true"
       readonly="false" scope="view"
           createObject="#{javascript:return new test.FruitController(session, database);}">
     </xe:objectData>
  </xp:this.data>

For a bean approach you would need to edit the faces-config.xml and change the class to have a parameterless constructor. For the select values you could stick with the toArray() call in your page or better change the class to return an array in the first place. I updated the class above accordingly (so you can still use EL, no need for SSJS).

Now you only need to add a refresh to the repeat in the onChange event of your selects. Since the new values will be send to the object data source (you bound them to colorFilter, shapeFilter, tasteFilter) the refresh will execute #{fruitController.selectedFruits} which delivers the subset back to the panel.

So the concept here is: You fetch all the user data once into the object data source and once that is loaded filter inside that class, no renewed retrieval or lookup required.

Let us know hoe it goes

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top