This discussion is archived
1 2 Previous Next 15 Replies Latest reply: Jan 21, 2013 6:30 PM by abhinay_a RSS

How to create a tableview for this ??

abhinay_a Newbie
Currently Being Moderated
Hey, i have been stuck to this part for a long time and not able to find any answers, may be i can get some help from you guys.

See i have a scenario where i have different "items",out of which each will belong to a predefined category. These items have names and then different types(depend on the category), each of these types in turn have a price.

For example if I have Item1(Bottle Of Water),belongs to category1 and it has different types : 200ml ,500 ml, 1000ml with price tags of 10,20,30 respectively.

Similarly,I have Item2(Box of Butter),belongs to category2 and it has different types : 100 gm, 200 gm with price tags of 15 and 25 respectively.

Now, i want these data to be shown on a editable table,when i press on a category button,all items belonging to the category(having the same types) must be displayed, with the headings divided into :


On pressing category 1


NAME | PRICE |
| 200ml |500ml|1000ml |
| |
Bottle Of Water | 10 | 20 | 30 |
. . . .
. . . .




On pressing category 2


NAME | PRICE |
| 100gm | 200ml |
| |
Box Of Butter | 15 | 25 |




So i want a dyanamic table which can have dynamic nested columns.

I was trying to make a table from the class

public class ItemVO()
{
String itemName;
List<ItemTypeVO> type;
}

and My ItemTypeVO has the following attributes :

public class ItemTypeVO()
{
String typeName;
Integer price;
}

But m not able to get anywhere with it.

Many a places i have found dynamic columns with attributes like this :

public class ItemVO()
{
String itemName;
ItemTypeVO type;
}

But this doesnt suit my functionality. i guess .

Can anybody help me with what change can i make in my class, so that i can make a table and supply me with some code as well.

Thanks in advance.
  • 1. Re: How to create a tableview for this ??
    James_D Guru
    Currently Being Moderated
    The key to these kinds of questions is always to get the data model right first. Once the data model is correct, the UI should fall out fairly easily.

    If I understand you correctly, each item belongs to a unique category. The list of types (200ml, 500ml, 1000ml; or 100gm, 200gm in your examples) depends on the category, but is fixed for all items within a given category. Each item in a given category specifies a price for each of the types determined by its category, and of course each item in the category will have a different price for any given type.

    So I think your object model should define a Type class (which might be as trivial as having a single String and a get method for it, but doing this allows you to add functionality to that class later if you need). Then define a Category class (possibly an enum if all the categories are fixed) whose API looks like this:
    public class Category {
      public List<Type> getTypes() {...}
    }
    and then your item class should do
    public class Item {
      public String getName();
      public Category getCategory();
      public int getPrice(Type type);
    }
    with the getPrice(Type types) method throwing an IllegalArgumentException if getCategory().getTypes().contains(type) is false.
    You'll probably implement this with JavaFX properties (so the table can bind to them) and something like a Map<Type, IntegerProperty> to store the prices. (If so, be sure to implement equals(...) and hashCode() properly for the Type class.)

    Now the UI more or less drops out, I think.
    Define the table with two columns: nameColumn and priceColumn. I assume you have a control (ChoiceBox or something similar) to pick a category, and also some mechanism for getting all items in a given category. When the user chooses a category, do the following:

    1. Clear the items from the table (table.getItems().clear()).
    2. Clear the nested columns from the priceColumn (priceColumn.getColumns().clear()).
    3. Rebuild the nested columns:
    for (final Type type : category.getTypes()) {
      TableColumn<Item, Integer> col = new TableColumn<Item, Integer>(type.getName());
      col.setCellValueFactory(new Callback<CellDataFeatures<Item>, ObservableValue<Integer>>() {
        @Override
        public void call(CellDataFeatures<Item> item) {
          return item.getValue().getPrice(type);
        }
      });
      priceColumn.add(col);
    }
    4. Set the items in the table with table.getItems().setAll(getItemsByCategory(category)); (or whatever your mechanism is for getting the items belonging to a given category).

    You should play around with this object design a bit before you start on the UI. For example, it's not particularly robust in that it's designed to throw exceptions if you request a price for the wrong type (one that isn't from the correct Category). If your list of categories is fixed, you might be able to come up with a nice design using subclasses of an abstract class Category and a generic Item class (Item<E extends Category>) as a more robust (but significantly more complicated) design.

    Edited by: James_D on Jan 17, 2013 5:56 PM
  • 2. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    Hey James, thanks for the vivd explanantion.

    Do you think that this approach will work fine if i want to make the nested columns as editable and hence take user input and persist it in the DB ?
  • 3. Re: How to create a tableview for this ??
    James_D Guru
    Currently Being Moderated
    I see no reason why not. You'd just need to define appropriate cell factories for the columns when you create them, but the structure would be the same.
  • 4. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    for (final Type type : category.getTypes()) {
      TableColumn<Item, Integer> col = new TableColumn<Item, Integer>(type.getName());
      col.setCellValueFactory(new Callback<CellDataFeatures<Item>, ObservableValue<Integer>>() {
        @Override
        public void call(CellDataFeatures<Item> item) {
          return item.getValue().getPrice(type);
        }
      });
      priceColumn.add(col);
    }
    i guess it should be
    for (final Type type : category.getTypes()) {
      TableColumn<Item, Integer> col = new TableColumn<Item, Integer>(type.getName());
      col.setCellValueFactory(new Callback<CellDataFeatures*<Item, Integer>*, ObservableValue<Integer>>() {
        @Override
        public *ObservableValue<Integer>* call(CellDataFeatures<Item> item) {
          return item.getValue().getPrice(type);
        }
      });
      priceColumn.add(col);
    }
    Now the question is how do i convert Integer to Observablevalue<Integer> ??

    Edited by: abhinay_a on Jan 19, 2013 1:55 AM
  • 5. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    One more Place where m stuck is when my priceColumn is
    TableColumn<ItemVO,Integer>  priceColumn = new TableColumn<ItemVO,Integer> ("Price");
                    quantity.setMinWidth(200);
                    quantity.setCellValueFactory(
                              new PropertyValueFactory<ItemVO, Integer>("price"));
    and then i use
     TableColumn<ItemVO, Integer> col = new TableColumn<ItemVO, Integer>(type.getTypeName());
    priceColumn.add(col);
    i get an error :

    The method add(TableColumn<ItemVO,Integer>) is undefined for the type TableColumn<ItemVO,Integer>
  • 6. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    We can either use ObservalueValue< Number > or use new ReadOnlyObjectWrapper< Integer >(integer value);

    I got this one :)

    But still stuck at the second one :(

    Edited by: abhinay_a on Jan 19, 2013 2:35 AM
  • 7. Re: How to create a tableview for this ??
    James_D Guru
    Currently Being Moderated
    Sorry,
    priceCol.getColumns().add(col);
    My bad..
  • 8. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    No worries, i did identify the problem :)

    Just one more question, when will the item in the following code be null, or more vividly, where does it get its value from ???
     @Override
        public void call(CellDataFeatures<Item> item) {
          return item.getValue().getPrice(type);
    Mt actual code is
    ObservableList<ItemVO> dataTable = FXCollections.observableArrayList();
                   dataTable = stockDetailsService.viewStock(categoryId);
                   
              
                    
                    
                    TableView<ItemVO> table1 = new TableView<ItemVO>();
                    table1.setEditable(false);
                    table1.setMaxSize(400, 300);
                    table1.setStyle("-fx-background-color: transparent;");
                    
                    
                    TableColumn<ItemVO,String> itemName = new TableColumn<ItemVO,String> ("Item");
                    itemName.setMinWidth(200);
                    itemName.setCellValueFactory(
                              new PropertyValueFactory<ItemVO, String>("itemName"));
                    
                    TableColumn<ItemVO, Integer>  quantity = new TableColumn<ItemVO, Integer> ("Quantity");
                    quantity.setMinWidth(200);
                    
                    for (final CategoryTypeVO type : typeList)
                    {
                           TableColumn<ItemVO, Integer> col = new TableColumn<ItemVO, Integer>(type.getTypeName());
                           col.setCellValueFactory(new Callback<CellDataFeatures<ItemVO,Integer>, ObservableValue<Integer>>() {
                             @Override
                             public ObservableValue<Integer> call(CellDataFeatures<ItemVO,Integer> item) 
                             {
                                  ObservableMap<String,ItemTypeVO> itemTypesMap = FXCollections.observableHashMap();
                                  itemTypesMap = item.getValue().getListType();
                                  
                                  return new ReadOnlyObjectWrapper<Integer>(Integer.parseInt(itemTypesMap.get(type.getTypeId()).toString()));
                                  
                             }
    
                           });
                           quantity.getColumns().add(col);
                         }
    
                    
                    table1.setItems(dataTable);
                    table1.getColumns().addAll(itemName, quantity);
    here my item.getValue().getListType(); is null !

    My itemVO is
    public class ItemVO 
    {
         private String itemId;
         private String itemName;
         private String categoryId;
         private ObservableMap<String,ItemTypeVO> listType;
    }
    and My ItemTypeVO is
    public class ItemTypeVO 
    {
         private String typeId;
         private String itemId;
         private Integer quantity;
         private Integer mrp;
    }
    Edited by: abhinay_a on Jan 19, 2013 6:32 AM
  • 9. Re: How to create a tableview for this ??
    James_D Guru
    Currently Being Moderated
    The cellValueFactory is an object that returns the value to display given a value for a given row. It's call(...) method will be invoked as and when necessary in order to display cells in the table. So the item is effectively passed in by JavaFX in order to display each cell in the table.

    For empty cells, and possibly others, item.getValue() will return null. So you probably need
    ItemVO itemVO = item.getValue();
    if (itemVO == null) {
      return null ;
    // or perhaps
    // return new ReadOnlyObjectWrapper<Integer>(null);
    } else {
      ObservableMap<String,ItemTypeVO> itemTypesMap = itemVO.getListType();
      return new ReadOnlyObjectWrapper<Integer>(Integer.parseInt(itemTypesMap.get(type.getTypeId()).toString()));
    }
  • 10. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    for (final Type type : category.getTypes()) {
      TableColumn<Item, Integer> col = new TableColumn<Item, Integer>(type.getName());
      col.setCellValueFactory(new Callback<CellDataFeatures<Item>, ObservableValue<Integer>>() {
        @Override
        public void call(CellDataFeatures<Item> item) {
          return item.getValue().getPrice(type);
        }
      });
      priceColumn.add(col);
    }
    In this block of coding type cannot be resolved inside call method. Is there any way of getting the value of type inside the call method??
  • 11. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    Cant even Solve this by making a static class, because the value that gets set in the message object is the last value of type in the loop :(
    public static class Test
          {
              static CategoryTypeVO type = null;
              public static void setType(CategoryTypeVO type)
              {
                   Test.type = type;
              }
    
              public static CategoryTypeVO getType()
              {
                        return type;
              }
          }
    Any other way that you might suggest ??

    Edited by: abhinay_a on Jan 20, 2013 3:57 AM
  • 12. Re: How to create a tableview for this ??
    James_D Guru
    Currently Being Moderated
    I mocked this up and it works fine for me. The variable 'type' is declared in the for loop, so it's in scope. As long as you declare it to be final, it will be accessible inside the anonymous inner class.
  • 13. Re: How to create a tableview for this ??
    James_D Guru
    Currently Being Moderated
    I don't really know what you are trying to do in this Test class.

    I mocked this up with the object model I suggested and a mock data access object which just hard codes a few items, and it works just fine.

    Type.java
    package itemtable;
    
    public class Type {
    
      private final String name ;
      
      public Type(String name) {
        this.name = name ;
      }
      
      public String getName() {
        return name ;
      }
    }
    Category.java
    package itemtable;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class Category {
    
      private final List<Type> types ;
      private final String name ;
    
      public Category(String name, List<Type> types) {
        this.name = name ;
        this.types = new ArrayList<Type>();
        this.types.addAll(types);
      }
      
      public List<Type> getTypes() {
        return Collections.unmodifiableList(types);
      }
      
      public String getName() {
        return name ;
      }
      
      @Override
      public String toString() {
        return name ;
      }
    }
    Item.java
    package itemtable;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.ReadOnlyObjectProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.beans.property.SimpleStringProperty;
    import javafx.beans.property.StringProperty;
    
    public class Item {
      private final StringProperty name ;
      private final ReadOnlyObjectProperty<Category> category ;
      private final Map<Type, DoubleProperty> prices ;
      
      public Item(String name, Category category, Map<Type, Double> prices) {
        this.name = new SimpleStringProperty(name);
        this.category = new SimpleObjectProperty<Category>(category);
        this.prices = new HashMap<Type, DoubleProperty>();
        for (Type type : prices.keySet()) {
          validateType(type);
          this.prices.put(type, new SimpleDoubleProperty(prices.get(type)));
        }
      }
      
      public String getName() {
        return name.get();
      }
      public Category getCategory() {
        return category.get();
      }
      public double getPrice(Type type) {
        validateType(type);
        return prices.get(type).get();
      }
      public void setName(String name) {
        this.name.set(name);
      }
      public void setPrice(Type type, double price) {
        validateType(type);
        prices.get(type).set(price);
      }
      public StringProperty nameProperty() {
        return name ;
      }
      public ReadOnlyObjectProperty<Category> categoryProperty() {
        return category ;
      }
      public DoubleProperty priceProperty(Type type) {
        return prices.get(type);
      }
      
      private void validateType(Type type) {
        final List<Type> allowedTypes = category.get().getTypes();
        if (! allowedTypes.contains(type)) {
          throw new IllegalArgumentException("Cannot set a price for "+type.getName()+": it is not a type for the category "+category.getName());
        }
      }
    }
    DAO.java
    package itemtable;
    
    import java.util.List;
    
    public interface DAO {
      public List<Category> getCategories();
      public List<Item> getItemsByCategory(Category category);
    }
    MockDAO.java
    package itemtable;
    
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class MockDAO implements DAO {
      
      private final List<Category> categories ;
      private final Map<Category, List<Item>> itemLookup ;
      
      public MockDAO() {
        final Type spreadType100g = new Type("100g");
        final Type spreadType200g = new Type("200g");
        final Type drinkType200ml = new Type("200ml");
        final Type drinkType500ml = new Type("500ml");
        final Type drinkType1000ml = new Type("1000ml");
        final Category spreads = new Category(
            "Spreads",
            Arrays.asList(spreadType100g, spreadType200g)
        );
        final Category drinks = new Category(
            "Drinks",
            Arrays.asList(drinkType200ml, drinkType500ml, drinkType1000ml)
        );
        final Map<Type, Double> waterPrices = new HashMap<Type, Double>();
        waterPrices.put(drinkType200ml, 10.0);
        waterPrices.put(drinkType500ml, 20.0);
        waterPrices.put(drinkType1000ml, 30.0);
        final Map<Type, Double> pepsiPrices = new HashMap<Type, Double>();
        pepsiPrices.put(drinkType200ml, 25.0);
        pepsiPrices.put(drinkType500ml, 45.0);
        pepsiPrices.put(drinkType1000ml, 75.0);
        final Map<Type, Double> butterPrices = new HashMap<Type, Double>();
        butterPrices.put(spreadType100g, 15.0);
        butterPrices.put(spreadType200g, 25.0);
        final Map<Type, Double> margarinePrices = new HashMap<Type, Double>();
        margarinePrices.put(spreadType100g, 12.0);
        margarinePrices.put(spreadType200g, 20.0);
        final Map<Type, Double> mayoPrices = new HashMap<Type, Double>();
        mayoPrices.put(spreadType100g, 20.0);
        mayoPrices.put(spreadType200g, 35.0);
        final Item water = new Item("Water", drinks, waterPrices);
        final Item pepsi = new Item("Pepsi", drinks, pepsiPrices);
        final Item butter = new Item("Butter", spreads, butterPrices);
        final Item margarine = new Item("Margarine", spreads, margarinePrices);
        final Item mayonnaise = new Item("Mayonnaise", spreads, mayoPrices);
        
        this.categories = Arrays.asList(drinks, spreads);
        this.itemLookup = new HashMap<Category, List<Item>>();
        itemLookup.put(drinks, Arrays.asList(water, pepsi));
        itemLookup.put(spreads, Arrays.asList(butter, margarine, mayonnaise));
      }
    
      @Override
      public List<Category> getCategories() {
        return categories ;
      }
    
      @Override
      public List<Item> getItemsByCategory(Category category) {
        return itemLookup.get(category);
      }
    
    }
    ItemTable.java
    package itemtable;
    
    import javafx.application.Application;
    import javafx.beans.value.ChangeListener;
    import javafx.beans.value.ObservableValue;
    import javafx.scene.Scene;
    import javafx.scene.control.ChoiceBox;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableColumn.CellDataFeatures;
    import javafx.scene.control.TableView;
    import javafx.scene.control.cell.PropertyValueFactory;
    import javafx.scene.layout.BorderPane;
    import javafx.stage.Stage;
    import javafx.util.Callback;
    
    public class ItemTable extends Application {
    
      @Override
      public void start(Stage primaryStage) {
        final DAO dao = new MockDAO();
        final ChoiceBox<Category> choiceBox = new ChoiceBox<Category>();
        choiceBox.getItems().setAll(dao.getCategories());
    
        final TableView<Item> table = new TableView<Item>();
        final TableColumn<Item, String> nameCol = new TableColumn<Item, String>("Name");
        nameCol.setCellValueFactory(new PropertyValueFactory<Item, String>("name"));
        final TableColumn<Item, Double> priceCol = new TableColumn<Item, Double>("Price");
        table.getColumns().addAll(nameCol, priceCol);
    
        choiceBox.getSelectionModel().selectedItemProperty()
            .addListener(new ChangeListener<Category>() {
              @Override
              public void changed(ObservableValue<? extends Category> observable, Category oldValue, Category newValue) {
                table.getItems().clear();
                priceCol.getColumns().clear();
                for (final Type type : newValue.getTypes()) {
                  final TableColumn<Item, Number> col = new TableColumn<Item, Number>(type.getName());
                  col.setCellValueFactory(new Callback<CellDataFeatures<Item, Number>, ObservableValue<Number>>() {
                    @Override
                    public ObservableValue<Number> call(CellDataFeatures<Item, Number> cellData) {
                      Item item = cellData.getValue();
                      if (item == null) {
                        return null;
                      } else {
                        return item.priceProperty(type);
                      }
                    }
                  });
                  priceCol.getColumns().add(col);
                }
                table.getItems().setAll(dao.getItemsByCategory(newValue));
              }
            });
    
        BorderPane root = new BorderPane();
        root.setTop(choiceBox);
        root.setCenter(table);
    
        Scene scene = new Scene(root, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
      }
    
      public static void main(String[] args) {
        launch(args);
      }
    }
  • 14. Re: How to create a tableview for this ??
    abhinay_a Newbie
    Currently Being Moderated
    Hey james thanks for such an explanatory answer..Thanks a lotttt !! Like Like..Super Like !! Just hope i could give you some more points ..
1 2 Previous Next

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points