Im using java enums to define how to render a modal window with buttons (Vaadin handles the rendering). My problem is that when I run the gui my buttons comes in a randomized order each time. So my question is this, since i use a enum set to hold my buttons, will that be unordered? whats the best way to make it into a ordered list?
My settings enum
public enum MODAL_SETTINGS {
NEW_MODAL_WINDOW("menu.context.new", "400", MODAL_BUTTON.SAVE, MODAL_BUTTON.CANCEL),
EDIT_MODAL_WINDOW("menu.context.modify","400", MODAL_BUTTON.UPDATE, MODAL_BUTTON.CANCEL),
DELETE_MODAL_WINDOW("menu.context.delete", "250", false, MODAL_BUTTON.DELETE, MODAL_BUTTON.CANCEL);
private EnumSet<MODAL_BUTTON> buttons;
private String caption;
private String width;
private boolean isResizable = true;
private MODAL_SETTINGS(String caption, String width, MODAL_BUTTON... buttons){
this.setCaption(caption);
this.setWidth(width);
this.buttons = EnumSet.copyOf(Arrays.asList(buttons));
}
private MODAL_SETTINGS(String caption, String width, boolean isResizable, MODAL_BUTTON... buttons){
this.setCaption(caption);
this.setWidth(width);
this.isResizable = isResizable;
this.buttons = EnumSet.copyOf(Arrays.asList(buttons));
}
public EnumSet<MODAL_BUTTON> getButtons(){
return buttons;
}
#Override
public String toString(){
String s = super.toString();
s=s.replaceAll("_", ".");
return s;
}
public void setCaption(String caption) {
this.caption = caption;
}
public String getCaption() {
return caption;
}
public void setWidth(String width) {
this.width = width;
}
public String getWidth() {
return width;
}
public boolean isResizable() {
return isResizable;
}
}
My buttons enum
public enum MODAL_BUTTON {
SAVE, UPDATE, CANCEL, DELETE;
}
Use Enum.values() instead of an EnumSet:
Note that each enum type has a static values method that returns an array containing all of the values of the enum type in the order they are declared. This method is commonly used in combination with the for-each loop to iterate over the values of an enumerated type.
Source: Enums in the Java 1.5 documentation
According to the documentation for Enumset, the iterator should return the Enum constants in the order in which they were declared.
The iterator returned by the iterator method traverses the elements in their natural order (the order in which the enum constants are declared). The returned iterator is weakly consistent: it will never throw ConcurrentModificationException and it may or may not show the effects of any modifications to the set that occur while the iteration is in progress.
Although it can be something about with your UI thread accessing the Enums in different orders.
From EnumSet documentation:
The iterator returned by the iteratormethod traverses the elements in their natural order (the order in which the enum constants are declared).
So, your problem is somewhere else.
You must have something in the place where you are actually assiging the buttons, which changes their order. Try to give us a SSCCE.
Check out the sample code below where each time you run it the order of buttons will be the same for all rows.
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class EnumeOrderButtonsTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPanel p = new JPanel(new GridLayout(0, MODAL_BUTTON.values().length));
int noOfRows = 10;
for(int i = 0; i < noOfRows; i++)
for(MODAL_BUTTON mb : MODAL_BUTTON.values())
p.add(new JButton(mb.name()));
JFrame f = new JFrame();
f.setContentPane(p);
f.setSize(800, 600);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
public enum MODAL_BUTTON
{
SAVE, UPDATE, CANCEL, DELETE;
}
}
Related
This is a complicated problem, and unfortunately a small SSCCE would not be possible. Therefore I have a long SCCE that demonstrates the problem. The simplified sample program uses a simplified data source - Time Zones. To use it, select a Time Zone in the table, then change the filter with the buttons at the top. Notice the text at the bottom changing to show the application selection. The undesired behavior is that when shifting to a filter that does not include the selected value, the selected value in the model is cleared. Surprisingly, when the selection is filtered out, the value is updated to being not set; but when filtered back in, the application selection is returned.
The Swing-based application's design is a Model-ViewModel-View, the Model is supposed to be the authoritative source for the application's data, including what the current selection is. The Model can have multiple ViewModel-Views displaying the data in the Model. This current selection may be reflected in multiple views. The user should be able to change the selection from any View. The selection may or may not be visible in all Views if it doesn't apply to some Views (a real-world example might be a View that shows vehicle maintenance may not show trips being taken by the vehicle).
The sample program has a JLabel as a simplified View-only of the application's selection, which displays at the bottom of the app the selection in the model.
The other more relevant View is a JTable that shows one Time Zone (as a String) per row. It has a custom ListSelectionModel as the ViewModel that forwards change requests to the application Model, and listens to changes from the application Model and applies them to the selection by calling methods on super. This works as expected, at least until filtering is applied.
The process of filtering is done mostly within the JTable and its inner classes, such as JTable$SortManager. It seems to remember and clear the selection, perform the sort and filter, and then restore the selection or nothing if the selected value is not in the newly filtered set.
Unfortunately, in the ListSelectionModel, these clearing and selecting operations are changing the underlying selection in the application Model. In my actual application, the selection loads a lot more information to display about the selection, and is a relatively expensive operation, so spurious changes to this value should be avoided.
So the question is this: Is there a way to prevent the application Model's selection from being changed when changing the table filter? I imagine the solution would fall under one of these categories:
There may be some way of detecting within the ListSelectionModel when the filter/sort is in progress, and not update the application model while that is happening
There may be something that can be overridden somewhere to change the undesired behavior
Here is the sample code:
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;
import javax.swing.Box;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
public class TableProblem
extends JFrame
{
public static final class ApplicationModel
{
String[] data = TimeZone.getAvailableIDs();
public String[] getData() { return data; }
private String modelSelection;
public String getModelSelection() { return modelSelection; }
public void setModelSelection(String value) { modelSelection = value; fireModelSelectionChange(); }
private void fireModelSelectionChange()
{ selectionListeners.forEach(l -> l.modelSelectionChanged(modelSelection, findModelIndex(modelSelection))); }
private int findModelIndex(String value)
{
if (value != null)
for (int i = 0; i < data.length; i++)
if (value.equals(data[i]))
return i;
return -1;
}
private List<ApplicationModelSelectionListener> selectionListeners = new ArrayList<>();
public void addSelectionListener(ApplicationModelSelectionListener l) { selectionListeners.add(l); }
}
public interface ApplicationModelSelectionListener
{
void modelSelectionChanged(String selection, int selectedModelIndex);
}
/** This class acts as the selection ViewModel. The actual model is the
* passed-in ApplicationModel.
*/
public final class TimeZoneListSelectionModel
extends DefaultListSelectionModel
implements ApplicationModelSelectionListener
{
private final ApplicationModel appMdl;
private static final long serialVersionUID = 1L;
private TimeZoneListSelectionModel(ApplicationModel appMdl)
{
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
this.appMdl = appMdl;
appMdl.addSelectionListener(this);
}
// Requests to ListSelectionModel to modify the selection are passed
// to the appMdl
#Override
public void clearSelection()
{
appMdl.setModelSelection(null);
}
#Override
public void setSelectionInterval(int index0, int index1)
{
int modelIdx = tbl.convertRowIndexToModel(index0);
String value = appMdl.getData()[modelIdx];
appMdl.setModelSelection(value);
}
#Override
public void addSelectionInterval(int index0, int index1)
{
int modelIdx = tbl.convertRowIndexToModel(index0);
String value = appMdl.getData()[modelIdx];
appMdl.setModelSelection(value);
}
// Notification from the app model about selection change gets
// percolated back to the user interface
#Override
public void modelSelectionChanged(String selection, int selectedModelIndex)
{
if (selectedModelIndex == -1)
{
super.clearSelection();
return;
}
int viewIndex = tbl.convertRowIndexToView(selectedModelIndex);
if (viewIndex == -1)
super.clearSelection();
else
super.setSelectionInterval(viewIndex, viewIndex);
}
}
public static final class TimeZoneTableModel
extends AbstractTableModel
{
private static final long serialVersionUID = 1L;
private final String[] data;
public TimeZoneTableModel(String[] data)
{
this.data = data;
}
#Override public int getRowCount() { return data.length; }
#Override public int getColumnCount() { return 1; }
#Override
public Object getValueAt(int rowIndex, int columnIndex)
{
if (columnIndex == 0)
return data[rowIndex];
throw new IllegalArgumentException("columnIndex="+columnIndex+" should be < 1");
}
#Override public String getColumnName(int column)
{ return "Time Zone"; }
}
private static final class StringRowFilter
extends RowFilter<TableModel, Integer>
{
private String prefix;
public void setPrefix(String value) { prefix = value; rowSorter.sort(); }
private final TableRowSorter<TableModel> rowSorter;
public StringRowFilter(TableRowSorter<TableModel> rowSorter)
{
this.rowSorter = rowSorter;
}
#Override
public boolean include(
Entry<? extends TableModel, ? extends Integer> entry)
{
if (prefix == null)
return true;
String lowerCase = entry.getStringValue(0).toLowerCase();
return lowerCase.startsWith(prefix);
}
}
private static final long serialVersionUID = 1L;
public static void main(String[] args)
{
ApplicationModel appMdl = new ApplicationModel();
SwingUtilities.invokeLater(() -> new TableProblem(appMdl).setVisible(true));
}
private final JTable tbl;
public TableProblem(ApplicationModel appMdl)
{
super("View-ModelView-Model Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TimeZoneTableModel mdl = new TimeZoneTableModel(appMdl.getData());
tbl = new JTable(mdl);
tbl.setAutoCreateRowSorter(true);
TimeZoneListSelectionModel tzListSelectionModel = new TimeZoneListSelectionModel(appMdl);
tbl.setSelectionModel(tzListSelectionModel);
#SuppressWarnings("unchecked")
TableRowSorter<TableModel> rowSorter = (TableRowSorter<TableModel>)tbl.getRowSorter();
StringRowFilter filter = new StringRowFilter(rowSorter);
rowSorter.setRowFilter(filter);
Box filterButtons = createFilterButtons(filter);
Box vbox = Box.createVerticalBox();
vbox.add(filterButtons);
vbox.add(new JScrollPane(tbl));
JLabel mdlSelect = new JLabel("App Model selection: ");
appMdl.addSelectionListener((selection, selectedModelIndex) ->
mdlSelect.setText("App Model selection: " + selection + " (" +
selectedModelIndex + ")"));
vbox.add(mdlSelect);
add(vbox, BorderLayout.CENTER);
pack();
}
private static Box createFilterButtons(StringRowFilter filter)
{
Box filterButtons = Box.createHorizontalBox();
filterButtons.add(new JLabel("Filter: "));
for (String filterStr : "All,Africa,America,Antarctica,Asia,Australia,Canada,Europe,Pacific,Us".split(","))
addFilterButton(filter, filterButtons, filterStr);
return filterButtons;
}
private static void addFilterButton(StringRowFilter filter,
Box filterButtons, String buttonName)
{
String filterPrefix = "All".equals(buttonName) ? null : buttonName.toLowerCase();
JButton asiaButton = new JButton(buttonName);
asiaButton.addActionListener(ae -> filter.setPrefix(filterPrefix));
filterButtons.add(asiaButton);
}
}
A disappointing and unsatisfying solution to this problem is upon requesting a change of the filter value, before telling the JTable's RowSorter, clear out the currently selected item in the model.
This is a change in the behavior, where the selection gets cleared, but prevents spurious clearing and resetting of the value when clicking through the filter values.
The change to the example code would involve passing the selection model to the action of the filter button. The three methods that would change are below.
public TableProblem(ApplicationModel appMdl)
{
super("View-ModelView-Model Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TimeZoneTableModel mdl = new TimeZoneTableModel(appMdl.getData());
tbl = new JTable(mdl);
tbl.setAutoCreateRowSorter(true);
TimeZoneListSelectionModel tzListSelectionModel = new TimeZoneListSelectionModel(appMdl);
tbl.setSelectionModel(tzListSelectionModel);
#SuppressWarnings("unchecked")
TableRowSorter<TableModel> rowSorter = (TableRowSorter<TableModel>)tbl.getRowSorter();
StringRowFilter filter = new StringRowFilter(rowSorter);
rowSorter.setRowFilter(filter);
Box filterButtons = createFilterButtons(filter, tzListSelectionModel);
Box vbox = Box.createVerticalBox();
vbox.add(filterButtons);
vbox.add(new JScrollPane(tbl));
JLabel mdlSelect = new JLabel("App Model selection: ");
appMdl.addSelectionListener((selection, selectedModelIndex) ->
mdlSelect.setText("App Model selection: " + selection + " (" +
selectedModelIndex + ")"));
vbox.add(mdlSelect);
add(vbox, BorderLayout.CENTER);
pack();
}
private static Box createFilterButtons(StringRowFilter filter,
TimeZoneListSelectionModel tzListSelectionModel)
{
Box filterButtons = Box.createHorizontalBox();
filterButtons.add(new JLabel("Filter: "));
for (String filterStr : "All,Africa,America,Antarctica,Asia,Australia,Canada,Europe,Pacific,Us".split(","))
addFilterButton(filter, filterButtons, filterStr, tzListSelectionModel);
return filterButtons;
}
private static void addFilterButton(StringRowFilter filter,
Box filterButtons, String buttonName,
TableProblem.TimeZoneListSelectionModel tzListSelectionModel)
{
String filterPrefix = "All".equals(buttonName) ? null : buttonName.toLowerCase();
JButton asiaButton = new JButton(buttonName);
asiaButton.addActionListener(ae -> {
tzListSelectionModel.clearSelection();
filter.setPrefix(filterPrefix);
});
filterButtons.add(asiaButton);
}
Notice I will not be marking this Answer as the solution because it is more of a workaround, and an actual solution to the problem is preferred.
I am currently creating a text adventure game and am displaying the player stats through the Jtable. I want to use the Jtable repaint method in order to make it display the stats as they change however I don't want to have to write the repaint method after every time I change it. How could I run the repaint() method whenever a stat changes and then continue with the rest of the code.
Here is the script running the Table that the repaint listener must be added:
import javax.swing.*;
import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
public class mainSystem extends JFrame{
public static class Stats{
public static int x = 1;
public static int n = 0;
public static ArrayList<String> contacts = new ArrayList<>(n);
public static int karma;
public static int SPC;
public static int OPC;
public static int wisdom;
public static int billsProposed;
public static int billsPassed;
public static String party;
public static String currentBill;
}
public static JTable statsWindow;
public mainSystem() {
String[] columns = new String[] {"Stat", "Value"};
Object[][] data = new Object[][]{{"Karma", Stats.karma}, {"SPC", Stats.SPC}, {"OPC", Stats.OPC}, {"Wisdom", Stats.wisdom}, {"Bills Proposed", Stats.billsProposed}, {"Bills Passed", Stats.billsPassed}, {"Party", Stats.party}};
statsWindow = new JTable(data, columns);
this.add(new JScrollPane(statsWindow));
this.setTitle("Stat Window");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
this.setVisible(true);
}
public static void addToContacts(String name){
Stats.n = Stats.n + 1;
Stats.contacts.add(name);
}
public static void table(){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new mainSystem();
}
});
}
public static void main(String[] args) throws InterruptedException {
while (Stats.x == 1){
int karmaChange = Stats.karma; int SPCChange = Stats.SPC; int OPCChange = Stats.OPC; int wisdomChange = Stats.wisdom; int billsProposedChanged = Stats.billsProposed; int billsPassedChanged = Stats.billsPassed;
intro.Introduction();
Section1.section1();
break;
}
if (Stats.x != 1){
End.loose();
} else{
End.win();
}
}
}
What you are looking for is the observer pattern, and it is not automatic:
You must create a listener interface (see for example ActionListener or MouseListener) with a method to be called when the int property change (hint: you may use PropertyChangeListener).
You must be able to register listener to your observable object. You may look at addXXXListener from any JComponent object (for example: addPropertyChangeListener or DefaultListModel). Registering is basically adding listener to a list of listener to "warn".
When you change the int value, you have to fire the event, cycling through the listeners bounds to your object. You can read the code of DefaultListModel for an example.
As for your question title:
Is there a way in java to, while a method is running, detect an
integer change and run said method, then return to the same spot
Unless you are running in several thread, if all you are doing is done on the same thread, then you will return to the same spot. Then again, you are using Swing, and you have the main thread and the EventDispatchThread (EDT): you may need to use SwingUtilities.invokeAndWait (if you are not on the EDT) to return to the same spot.
So, I want to access one class's data member's field from a whole other class through reflection. I have been unable to figure out, how, after I get the data member through reflection, I can change the field's value. I don't really know how to express it better, so I will let the code speak for me.
Here follows the handler class that calls the buttons. Following are the rest of the classes, whose functionality I will explain on the go.
import java.awt.*;
import java.awt.event.*;
public class SimHandler extends Frame{
public myValveButton but0,but1,but2,but3,but4,but5,but6,but7;
public SimHandler(){
super("Liquer Plant Control Panel");
this.setLayout(null);
this.setFont(new Font("Helvetica", Font.PLAIN, 14));
this.setBackground(Color.black);
but0 = new myValveButton("S1a",100,40,this);
but1 = new myValveButton("S1b",100,140,this);
but2 = new myValveButton("S2a",200,40,this);
but3 = new myValveButton("S2b",200,140,this);
but4 = new myValveButton("S3a",100,240,this);
but5 = new myValveButton("S3b",100,340,this);
but6 = new myValveButton("S4a",200,240,this);
but7 = new myValveButton("S4b",200,340,this);
this.setSize(335,410);
this.setLocation(100,100);
this.setVisible(true);
this.toFront();
this.setResizable(false);
this.addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
This is actually where I try to use reflection to change the value of Silo's instance state. Below this one follow LiqPlantSim and Silo classes. As you can see, the state variable cannot be resolved that way, and after quite some googling, I can't figure out how I can make it work.
import java.awt.Button;
import java.awt.Frame;
import java.awt.event.*;
import java.lang.reflect.Field;
public class myValveButton extends Button{
String label;
public myValveButton(String label,int x,int y,Frame f){
super(label);
this.label = label;
this.addActionListener(new myValveButtonHandler(label));
f.add(this);
this.setBounds(x, y, 35, 30);
}
}
class myValveButtonHandler implements ActionListener{
Field f;
String label;
public myValveButtonHandler(String label){
this.label = label;
}
public void actionPerformed(ActionEvent pushButton){
try {
f = LiqPlantSim.class.getDeclaredField("silo"+label.split("")[1]);
System.out.println(f);
//f.state = "full" //Eclipse says 'state cannot be resolved to a type or is not a field'
} catch (NoSuchFieldException e) {
} catch (SecurityException e) {
}
System.out.println(label.split("")[2]);
}
}
Here is the LiqPlantSim class.
import java.util.HashMap;
import java.util.Map;
public class LiqPlantSim{
public Silo silo1,silo2,silo3,silo4;
public Pipe pipe;
public LiqPlantSim(){
silo1 = new Silo(false,false);
silo2 = new Silo(false,true);
silo3 = new Silo(true,false);
silo4 = new Silo(true,true);
pipe = new Pipe();
}
}
Here is the Silo Class.
public class Silo {
public boolean mixer,resistance;
public String state,mixerState,resState;
public Silo(boolean mix,boolean res){
mixer = mix;
resistance = res;
state = "empty";
}
}
Apart from finding out how I can access the silo's state variables, I would really appreciate any feedback and/or advice on how I could structure my work better, and on any mistakes I might have made.
First off, Class#getDeclaredField(String) returns a Field object, not the actual value of that field. To get the value, you must use Field#get(Object), where the argument is an instance of the class for which you are trying to access a field. In your code, this would be:
LiqPlantSim sim = doSomethingToGetInstance();
f = LiqPlantSim.class.getDeclaredField("siloX");
Silo silo = (Silo) f.get(sim);
Which brings me to my next point: Why use reflection at all? Your answer probably has to do with getting the correct Silo using the label. You should* restructure LiqPlantSim to use an array or a List to solve this:
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
public class LiqPlantSim{
private List<Silo> silos;
public Pipe pipe;
public LiqPlantSim(){
silos = new ArrayList<>();
silos.add(new Silo(false,false));
silos.add(new Silo(false,true));
silos.add(new Silo(true,false));
silos.add(new Silo(true,true));
pipe = new Pipe();
}
public Silo getSilo(int index) {
return silos.get(index);
}
//Possibly other methods to access silos
}
(EDIT: You should do the same with the buttons in SimHandler)
Then, in the handler, you can access a Silo like this:
public void actionPerformed(ActionEvent pushButton){
try {
int labelLength = label.length();
int index = Integer.parseInt(label.substring(labelLength - 1, labelLength));
Silo silo = doSomethingToGetLiqPlantSimInstance().getSilo(index);
silo.state = "full" //Note that it is good practice to use private fields and public getters and setters
} catch (NoSuchFieldException e) {
} catch (SecurityException e) {
}
System.out.println(label.split("")[2]);
}
Better yet, get the index in the constructor and store it so you don't have to recalculate it every time.
*Of course, this is only a suggestion
Why do you implement your action-listeners in the button class? The ValveButton should not be aware of what to do when it is clicked.
Instead you should implement your action-listener in your SimHandler class. After instantiating your 8 ValveButtons you can add the action-listeners in a loop.
Anyways - if you really need to go for a solution using reflection I would recomment using the tiny PrivilegedAccessor framework. Though recommended to be used only in unit-tests it might be useful in your case.
This: "silo"+label.split("")[1] will create the String siloS.
To get the number from the label variable try: label.substring(1,2)
Also you don't need to pass the Frame f to the myValveButton constructor, you can add the buttons to the frame directly in the SimHandler using this.add(but0).
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
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
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class Test1{
JComboBox combo;
JTextField txt;
public static void main(String[] args) {
Test1 b = new Test1();
}
public Test1(){
String degrees[] = {"AAS1","AAS2","AAS1","AAS3"};
JFrame frame = new JFrame("Creating a JComboBox Component");
JPanel panel = new JPanel();
combo = new JComboBox(degrees);
combo.setEditable(true);
combo.setBackground(Color.gray);
combo.setForeground(Color.red);
txt = new JTextField(10);
txt.setText("1");
panel.add(combo);
panel.add(txt);
frame.add(panel);
combo.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent ie){
txt.setText(String.valueOf(combo.getSelectedIndex()+1));
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400,400);
frame.setVisible(true);
} }
As you see from the code above. I have JComboBox with 4 items. If there are no items that are same everything is OK.
But in my example ("AAS1","AAS2","AAS1","AAS3") first and third items are same, and I have problems in this case.
When I select any item I want to get it's index in JTextField, but when I select third item I get index of first item.
Has any idea?
That's because JComboBox is using equals to check the item equality. In your case, those two String are equal so it returns the first index that match. If you really need to do that, you might need to define your own item class like this:
private static class MyItem {
private String value;
public MyItem(String value) {
this.value = value;
}
public String getValue() {
return value;
}
#Override
public String toString() {
return value; //this is what display in the JComboBox
}
}
And then add the item like this:
MyItem degrees[] = {new MyItem("AAS1"),new MyItem("AAS2"),new MyItem("AAS1"),new MyItem("AAS3")};
JComboBox combo = new JComboBox(degrees);
Create a class like that:
class ComboItem{
private String name;
public ComboItem(String name){
this.name = name;
}
public String toString() {
return name;
}
}
and create your combobox:
comboBox = new JComboBox(new ComboItem[]{
new ComboItem("AAS1"),
new ComboItem("AAS2"),
new ComboItem("AAS1"),
new ComboItem("AAS3")
});
You have to separate how the equals is calculated on the Strings items and the effective representation. I think that this can be done just by creating a specific class for your purpose and use it instead that String.
Since this could be homework I'm not going to give exact result, just think about how the JComboBox internally chooses the index specified.
try using combo.getSelectedItem() instead. Since its two different strings in the String Array, you should be able to do a reference comparison and tell a difference between the two.