I am still new to Vaadin so, please bear with it.
We are currently migrating from Vaadin framework 8.0 to 8.3.2. One of the reasons of doing is that there's a requirement of using tree for the menu. Since 8.0 doesn't have tree, the workaround for generating a menu is by instantiating an inner Button class with the help of an Enum class in a loop (for user permission control):
public final class ValoMenuItemButton extends Button {
private static final String STYLE_SELECTED = "selected";
private final DashboardViewType view;
public ValoMenuItemButton(final DashboardViewType view) {
this.view = view;
setPrimaryStyleName("valo-menu-item");
setIcon(view.getIcon());
setCaption(view.getViewName().substring(0, 1).toUpperCase()
+ view.getViewName().substring(1));
DashboardEventBus.register(this);
addClickListener(new ClickListener() {
#Override
public void buttonClick(final ClickEvent event) {
UI.getCurrent().getNavigator()
.navigateTo(view.getViewName());
}
});
}
#Subscribe
public void postViewChange(final PostViewChangeEvent event) {
removeStyleName(STYLE_SELECTED);
if (event.getView() == view) {
addStyleName(STYLE_SELECTED);
}
}
}
The enum class structure is built in this manner:
AUDIT("Receipt Validation", RcptValidation.class, FontAwesome.BAR_CHART_O, false),
AUDIT1("Matching - Receipt not in SYCARDA", RcptNotInSycarda.class, FontAwesome.BAR_CHART_O, false),
AUDIT2("Matching - Receipt not in POS", RcptNotInPos.class, FontAwesome.BAR_CHART_O, false),
AUDIT3("Missing Sequence", MissSequence.class, FontAwesome.BAR_CHART_O, false),
AUDIT4("*Debug Purposes", LineAmtVsTotal.class, FontAwesome.BAR_CHART_O, false);
private DashboardViewType(final String viewName,
final Class<? extends View> viewClass, final Resource icon,
final boolean stateful) {
this.viewName = viewName;
this.viewClass = viewClass;
this.icon = icon;
this.stateful = stateful;
}
So far, I've not found any examples that are written around the v8 framework, while most of the sample code that I've seen are based on v7 framework.
I've attempted to write something like this, but the tree sub menus do not come out as it is (I've left out the expand and collapse event as that can be handled later).
My attempted code on the tree is this:
TreeData <String> treeData = new TreeData();
treeData.addRootItems("Dashboard","Sales","Sales Pattern","Top SKUs","Audit");
// The loop starts here (for DashboardViewType view: DashboardViewType.values)
if(enabled){
if(StringUtils.startsWith(view.getViewName(), "SALES")){
if (StringUtils.contains(view.getViewName(),"SALES_PATTERN")){
treeData.addItem( "Sales Pattern", view.getViewName());
}else{ treeData.addItem( "Sales", view.getViewName());
}
}else if (StringUtils.startsWith(view.getViewName(), "TOP_SKUS")){
treeData.addItem( "Top SKUs", view.getViewName());
}else if (StringUtils.startsWith(view.getViewName(), "AUDIT")){
treeData.addItem( "Audit", view.getViewName());
}else if (StringUtils.startsWith(view.getViewName(), "DASHBOARD")){
treeData.addItem( "Dashboard", view.getViewName());
}
DashboardEventBus.register(view);
}
// loop ends here
Tree<String> tree = new Tree<>("Sycarda Dashboard");
tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.setItemIconGenerator(item -> { return FontAwesome.BAR_CHART_O; });
tree.expand("Sales","Sales Pattern","Top SKUs","Audit");
tree.addSelectionListener(e -> new Button.ClickListener() {
#Override public void buttonClick(Button.ClickEvent event) {
DashboardEventBus.register(event);
UI.getCurrent().getNavigator().navigateTo(event.getClass().getName());
}
});
This was posted originally at the Vaadin forum, but since there were no answers to that, I am putting it here. I'd appreciate if there's any input or another approach for this problem.
Thanks in advance.
In Vaadin 8 you can simply define the "get children" method when adding the data. In your case the enum class should provide some method like "getSubItems", which you could then set as the value provider. The following example shows it in a similar way, where "rootItems" is simply the same as your top level enum instances and MenuItem the same as your enumeration.
static {
rootItems = Arrays.asList(...);
}
#PostConstruct
private void init() {
Tree<MenuItem> tree = new Tree<>();
tree.setItems(rootItems, MenuItem::getSubItems);
}
private class MenuItem {
private String name;
private Resource icon;
private Collection<MenuItem> subItems;
public Collection<MenuItem> getSubItems() {
return subItems;
}
// ... other getter and constructor omitted;
}
Someone has shown an example and it is similar to what Stefan mentioned. In context with my requirement, the steps involved include:
Create a wrapper class that includes:
private DashboardViewType view;
private Resource icon;
private boolean stateful;
private Class<? extends View> viewClass;
private String viewName;
//Create the get / set methods for those attributes above
//Constructor for the wrapper class is below.
public TreeMenuItem(DashboardViewType view){
this.view = view;
}
For the Enum class additional main menu items are added. Default main class can be used since you can't put a null.
public enum DashboardViewType {
SALES("Sales",DashboardView.class,FontAwesome.HOME,false),
SALES_PATTERN("Sales Pattern",DashboardView.class,FontAwesome.HOME,false),
TOP_SKUs("Top SKUs",DashboardView.class,FontAwesome.HOME,false),
AUDIT("Audit",DashboardView.class,FontAwesome.HOME,false)
}
The tree is built in this manner:
private Component buildTree(){
Tree<TreeMenuItem> tree = new Tree<>();
TreeData<TreeMenuItem> treeData = new TreeData<>();
//This is for items that have no child.
TreeMenuItem dashboardItem = new TreeMenuItem(DashboardViewType.DASHBOARD);
dashboardItem.setIcon(VaadinIcons.HOME_O);
dashboardItem.setStateful(DashboardViewType.DASHBOARD.isStateful());
dashboardItem.setViewName(DashboardViewType.DASHBOARD.getViewName());
treeData.addItem(null, dashboardItem);
for (DashboardViewType type : DashboardViewType.values()) {
TreeMenuItem menuItem = new TreeMenuItem(type);
menuItem.setIcon(VaadinIcons.HOME_O);
menuItem.setViewName(type.getViewName());
menuItem.setStateful(false);
treeData.addItem(null, menuItem);
getSubMenuItems(type).forEach(subView -> {
TreeMenuItem subItem = new TreeMenuItem(subView);
subItem.setViewName(subView.getViewName().substring(0, 1).toUpperCase()
+ subView.getViewName().substring(1));
subItem.setIcon(subView.getIcon());
subItem.setStateful(subView.isStateful());
subItem.setView(subView);
subItem.setViewClass(subView.getViewClass());
treeData.addItem(menuItem, subItem);
});
}
}
tree.setDataProvider(new TreeDataProvider<>(treeData));
tree.setItemIconGenerator(TreeMenuItem::getIcon);
tree.setItemCaptionGenerator(TreeMenuItem::getViewName);
tree.addItemClickListener((Tree.ItemClick<TreeMenuItem> event) -> {
DashboardEventBus.register(event.getItem().getView()); UI.getCurrent().getNavigator().navigateTo(event.getItem().getViewName());
});
}
The logic to create subviews:
private List getSubMenuItems(DashboardViewType type) {
List<DashboardViewType> dashboardList;
switch(type){
case TOP_SKUs:
dashboardList = new LinkedList<>(Arrays.asList(DashboardViewType.TOP_SKUs1,
DashboardViewType.TOP_SKUs2,
DashboardViewType.TOP_SKUs3,
DashboardViewType.TOP_SKUs4));
filterByUserLevel(dashboardList,subACL4);
return dashboardList;
case AUDIT:
dashboardList = new LinkedList<>(Arrays.asList(DashboardViewType.AUDIT1,
DashboardViewType.AUDIT2,
DashboardViewType.AUDIT3,
DashboardViewType.AUDIT4,
DashboardViewType.AUDIT5));
filterByUserLevel(dashboardList,subACL5);
return dashboardList;
case DASHBOARD:
break;
default:
break;
}
return Collections.emptyList();
}
Add additional cases if required so. After that, the function controls remove the elements that are not part of the user level:
private List<DashboardType> filterByUserLevel(List<DashboardType>list, String u){
if(list.size() == subACL.length()){
for(int i=0; i<list.size(); i++){
if(StringUtils.substring(subACL, i, i+1).equalsIgnoreCase("0")){
list.remove(i);
}
}
Collections.sort(list);
return list;
//this removes unwanted sub-menu items according current user level.
}
}
Related
I am building an app that will be submitting the details of your siblings to the database.
MY idea is since i dont know number of your children, i just have a floating button that am using to call a class that adds a contaner with some textFields to be filled.
so I have like a Form here....
private Button btnSubmit;
private Container cnt_box;
public class ChildrenForm extends Form
{
private List<Child> listofchildren;
public ChildrenForm()
{
super("CHILDREN DETAILS",BoxLayout.y());
FloatingActionButton fab=FloatingActionButton.createFAB(FontImage.MATERIAL_ADD);
fab.bindFabToContainer(this);
fab.addActionListener((e) -> addNewChild());
getToolbar().addMaterialCommandToRightBar("", FontImage.MATERIAL_CLEAR_ALL, (e) ->
clearAll());
btnSubmit=new Button("Submit");
cnt_box = new Container(new BoxLayout(BoxLayout.Y_AXIS));
cnt_box.add(btnSubmit);
add(cnt_box);
}
//....here i have some other methods...
}
i have a method to enable the editing here....
public void edit()
{
txtname.startEditingAsync();
txtname3.startEditingAsync();
txtbirth.startEditingAsync();
txtdbirth.startEditingAsync();
}
the floatingAction Button calls this method here....
public void addNewChild()
{
Childdetails td=new Childdetails("","","","",false);
add(td);
revalidate();
td.edit();
}
that method now called this class which i want to take the details showing this container.....
public class Childdetails extends Container
{
private TextField txtname;
private TextField txtname3;
private TextField txtbirth;
private TextField txtdbirth;
private CheckBox done=new CheckBox();
private Container cnt_child;
public Childdetails(String name,String name3,String birthcertno,String dateofbirth ,boolean checked)
{
super(new BorderLayout());
cnt_child=new Container();
cnt_child.addComponent(new Label("First Name"));
txtname = new TextField(name);
txtname.setHint("First Name");
cnt_child.addComponent(txtname);
cnt_child.addComponent(new Label("Surname"));
txtname3 = new TextField(name3);
txtname3.setHint("Surname");
cnt_child.addComponent(txtname3);
cnt_child.addComponent(new Label("Birth Certificate/Notification No"));
txtbirth = new TextField(birthcertno);
txtbirth.setHint("Birth Certificate No:");
cnt_child.addComponent(txtbirth);
cnt_child.addComponent(new Label("Date of Birth"));
txtdbirth = new TextField(dateofbirth);
txtdbirth.setHint("dd/MM/yyyy");
cnt_child.addComponent(txtdbirth);
add(CENTER,cnt_child);
add(LEFT,done);
done.setSelected(checked);
}
public void edit()
{
txtname.startEditingAsync();
txtname3.startEditingAsync();
txtbirth.startEditingAsync();
txtdbirth.startEditingAsync();
}
public boolean isChecked(){
return done.isSelected();
}
public String getText(){
return txtname.getText();
}
}
this is the method which am using to delate any selected container....but i understand its because of that save method......
private void clearAll()
{
int cc=getContentPane().getComponentCount();
for(int i=cc-1; i>=0; i--)
{
Childdetails t=(Childdetails)getContentPane().getComponentAt(i);
if(t.isChecked())
{
t.remove();
}
}
save();
getContentPane().animateLayout(300);
}
the save method....which after following some tutorial i believe its saving the taken data.... here
private void save()
{
listofchildren = new ArrayList<>();
Childdetails detail=new Childdetails("","","","",false);
Child child=new Child()
.name.set(detail.getText())
.name3.set(detail.getText())
.birthcertno.set(detail.getText())
.dateofbirth.set(detail.getText())
.checked.set(detail.isChecked());
listofchildren.add(child);
PropertyIndex.storeJSONList("child.json", listofchildren);
}
i also have a class i constructed following certain tutorial to save the data.....here
public class Child implements PropertyBusinessObject
{
public final Property<String,Child> name=new Property<>("firstname","");
public final Property<String,Child> name3=new Property<>("Surname","");
public final Property<String,Child> birthcertno=new Property<>("BirthCertNo","");
public final Property<String,Child> dateofbirth=new Property<>("dateofbirth","");
public final BooleanProperty<Child> checked=new BooleanProperty<>("checked", false);
private final PropertyIndex idx=new PropertyIndex(this,"Todo", name, name3, birthcertno, dateofbirth, checked);
#Override
public PropertyIndex getPropertyIndex(){
return idx;
}
now my main main problem... i just want when that submit button is pressed, to send the filled details..... i tried this,,,
btnSubmit.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent evt)
{
Log.p("Button pressed", 1);
save();
Log.p("data saved...", 1);
if(existsInStorage("child.json"))
{
Log.p("loading data ...", 1);
listofchildren=new Child().getPropertyIndex().loadJSONList("child.json");
String NationalID=Storage.getInstance().readObject("NationalID").toString();
String UserName=Storage.getInstance().readObject("UserName").toString();
Hashtable hash=new Hashtable();
hash.put("ChildDet", listofchildren);
hash.put("ReadIdCopy", NationalID);
hash.put("UserName",UserName);
final Result res=Result.fromContent(hash);
final String checkthis=res.toString();
//--------check url......
String myUrl="http://localhost:50111/AddChildren";
String Reply="";
requestclass c=new requestclass();
try {
Reply=c.checking(checkthis,myUrl);
} catch (IOException ex) {
// Logger.getLogger(AddChildren.class.getName()).log(Level.SEVERE, null, ex);
} catch (requestclass.JSONException ex) {
// Logger.getLogger(AddChildren.class.getName()).log(Level.SEVERE, null, ex);
}
if(Reply.equals("SuccesfullyRecieved"))
{
Dialog.show("SuccesfullyRecieved", "Details Succesfuly Recieved", "OK", null);
/*----redirect---*/
nextofkin nkin=new nextofkin();
nkin.nxtofkscreen();
}
else if(Reply.equals("sorry"))
{
Dialog.show("SORRY!!!", "Seems their is a problem updating Next of kin details... try again", "OK", null);
}
else
{
Dialog.show("Error", "Something went wrong, try checking your connection and try again later.", "OK", null);
}
}
else
{
ToastBar.showErrorMessage("Sorry, no data to submit....");
}
}
});
i dont know how to do it,,,, also my save method has some errors...please help me out, thanks in advance
This is caused by this line:
Childdetails t=(Childdetails)getContentPane().getComponentAt(i);
What you are doing here is looping over all the components in the content pane and downcasting them to Childdetails.
This is bad. You don't check instanceof which would be helpful. You might have other problems but this line:
add(cnt_box);
Specifically adds a non Childdetails component to the content pane (doing add without a context on a Form implicitly adds to the content pane).
Also about startEditingAsync. This is wrong.
This isn't the way to make them visible.
Notice your code adds a lot of components before the form is shown and uses animateLayout on these instances. This is probably why things aren't visible since you do that on a Form that isn't shown yet (from the constructor) and so the animation "runs" without any effect. The components are probably in the wrong area.
I suggest removing that whole block of startEditingAsync and also try:
if(getContentPane().isInitialized()) {
getContentPane().animateLayout(300);
}
Basically the idea is that I need some sort of session/cookie imitation for Java. I have to develop some kind of a blogging program for a university project. It does not have to be for Web, I mean that I don't need servlet's and other Java EE stuff. The whole interface has to be in the console.
So here is the problem, I've developed a Menu class in which I add a MenuItem object for every menu option I want to be added, after I navigate my menu and I want to log into my account, I need something like a session, otherwise I cannot hide the "Login" and "Register" options, and can't decide whether to show the "Logout" option. Since the Menu class is instantiated only once, there is no way it could be updated on the go(or at least I think so, still learning Java).
If there is someone who can give me an advice or an idea, that would help me a lot.
Here is the Menu class:
package my.app;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import my.toolbox.InputHandler;
public class Menu extends MenuItem {
protected SessionImitator SESSION = SessionImitator.getInstance();
protected String title;
private static final String OUT_OF_RANGE = "Please select an option within the range";
private static final MenuItem SEPARATOR = new MenuItem("---------------------");
private static final MenuItem BACK = new MenuItem("Go Back");
private static final MenuItem EXIT = new MenuItem("Exit", new Runnable() {
public void run() {
System.exit(0);
}
});
List<MenuItem> items;
public Menu(String title, MenuItem ... items) {
this(title, false, true, items);
}
public Menu(String title, boolean addBack, boolean addExit, MenuItem ... items) {
super(title);
setExecutable(this);
init(addBack, addExit, items);
}
private void init(boolean addBack, boolean addExit, MenuItem ... items) {
this.items = new ArrayList<MenuItem>(Arrays.asList());
for (MenuItem item : items) {
if (item.isSessionDependent()) {
if (!item.getSessionAction() && SESSION.isSet()) {
continue;
}
}
this.items.add(item);
}
if (SESSION.isSet()) {
System.out.println("THIS PART DOES NOT WORK SINCE THE OBJECT IT's NOT UPDATED AFTER THE SESSION IS SET!");
}
if (addBack) this.items.add(BACK);
if (addExit) this.items.add(EXIT);
}
private void display() {
int option = 0;
System.out.println(SEPARATOR.getTitle());
System.out.println(getTitle() + ":");
System.out.println(SEPARATOR.getTitle());
for (MenuItem item : items) {
System.out.println((option++) + ": " + item.getTitle());
}
System.out.println(SEPARATOR.getTitle());
System.out.println("select an option: ");
System.out.flush();
}
private MenuItem prompt() {
display();
int option = InputHandler.readInt();
if (option >= 0 && option < items.size()) {
return items.get(option);
}
System.out.println(OUT_OF_RANGE);
return null;
}
public void run() {
try {
for (MenuItem item = prompt(); item.isExecutable(); item = prompt()) {
item.run();
}
} catch (Throwable t) {
t.printStackTrace(System.out);
}
}
}
The MenuItem class:
package my.app;
public class MenuItem implements Runnable {
private String title;
private Runnable executable;
private boolean sessionDependent;
private boolean sessionAction;
protected MenuItem(String title) {
this(title, null, false, false);
}
protected MenuItem(String title, boolean sessionDependent, boolean sessionAction) {
this(title, null, sessionDependent, sessionAction);
}
protected MenuItem(String title, Runnable executable) {
this(title, executable, false, false);
}
public MenuItem(String title, Runnable executable, boolean sessionDependent, boolean sessionAction) {
this.title = title;
this.executable = executable;
this.sessionDependent = sessionDependent;
this.sessionAction = sessionAction;
}
public void run() {
try {
executable.run();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
public boolean isExecutable() {
return executable != null;
}
protected void setExecutable(Runnable executable) {
this.executable = executable;
}
public String getTitle() {
return title;
}
public boolean isSessionDependent() {
return sessionDependent;
}
public boolean getSessionAction() {
return sessionAction;
}
}
And the SessionImitator class:
package my.app;
public class SessionImitator {
private static SessionImitator instance = null;
protected int userId;
protected boolean locked = false;
protected SessionImitator() {
}
public static SessionImitator getInstance() {
if (instance == null) {
instance = new SessionImitator();
}
return instance;
}
public int getUserId() {
return userId;
}
public void setUserId(String id) {
if (!locked) {
this.userId = Integer.parseInt(id);
}
}
public boolean isSet() {
return locked;
}
}
What you are describing seems like you want to save a "state" of the program. But specifically of the state of the menu.
A simple implementation is to make your menu serializable, then save the serialized menu object to a file and reload it on login, if the file exists.
The above example would work for a user on a computer. If you wanted to persist across networks, etc. You might want to write the serialized object to a blob in a db table and then load it from there.
If your program is standalone, what you need is a container to hold current state of the application. There are different ways to obtain that:
a singleton: a dedicated class stores the state in its attributes and has a static member of its own class. That way you can get access to the state with code like State state = State.getInstance() - that is what your SessionImitator currently is.
a simple object injected in all other classes that could need it. A way to build that is to create an instance of a class holding state and pass it (for example in constructor) of other classes:
State state = new State();
...
Menu menu = new Menu(state);
a dependancy injection framework like Spring or Guice. It can help you to use the injection pattern but is really worthy when you have complex dependencies.
I've got a project written in JavaFX and I'm trying to get a refresh on a tableview without result.
I've googled around and tried some examples I've found but it still doesn't work.
I populate a tableview with information each row in this table can have new comments added to by double click on the row. The a new Tabpane is opened and the new comment can be added there. On close of this tabpane I'd like the one I clicked from to be refreshed.
I must be doing something wrong. I just don't know what.
In my StoreController
private void populateTableView(List<Store> stores) {
ObservableList<Store> data = FXCollections.observableArrayList(stores);
storeNumberColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("id"));
storePhoneColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("phoneNbr"));
chainColumn.setCellValueFactory(
new PropertyValueFactory<Store, String>("chainId"));
commentColumn.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<Store, ImageView>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Store, ImageView> p) {
Integer numberOfComments = p.getValue().getCommentsCount();
ReadOnlyObjectWrapper wrapper = null;
if (numberOfComments == 0) {
wrapper = null;
} else if (numberOfComments == 1) {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_SINGLE_FLAG_SOURCE));
} else {
wrapper = new ReadOnlyObjectWrapper(new ImageView(COMMENT_DOUBLE_FLAG_SOURCE));
}
return wrapper;
}
});
storeTable.setItems(data);
sortTable(storeTable, missedColumn);
}
#FXML
public void handleTableAction(MouseEvent event) {
if (event.getClickCount() == 2) {
showNewCommentStage();
}
}
private void showNewCommentStage() {
initCommentController();
Store store
= storeTable.getSelectionModel().selectedItemProperty().getValue();
commentController.showNewStage(commentPane, store);
}
It seems like the call-function doesn't get called when the commentpane is closed.
CommentController
public void showNewStage(Pane pane, Store store) {
this.store = store;
initStage(pane);
windowHandler = new WindowHandler(stage);
effectHandler.playEffect(pane);
constructCommentHeaders();
List<Comment> comments;
comments = commentService.listByStoreId(store.getId());
populateCommentTable(comments);
}
Like I said I've tried a lot of the solutions found here on Stackoverflow but with no results. The Tableview doesn't refresh. The Stores and the Comments are in different database tables if that's important
Can someone point me in the right direction?
Thanks!
****EDIT****
The Store.class
public class Store extends CommentEntity {
private String id;
private String chainId;
private String phoneNbr;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
public String getPhoneNbr() {
return phoneNbr;
}
public void setPhoneNbr(String phoneNbr) {
this.phoneNbr = phoneNbr;
}
#Override
public String toString() {
return "Store{" + "id=" + id + ", chainId=" + chainId + '}';
}
#Override
public String getCommentIdentifier() {
return id;
}
}
The CommentEntity.Class
public abstract class CommentEntity {
private int commentsCount;
public int getCommentsCount() {
return commentsCount;
}
public void setCommentsCount(int commentsCount) {
this.commentsCount = commentsCount;
}
public abstract String getCommentIdentifier();
}
Thank you for input, I hadn't even reflected over the ImageView / String.
Two issues:
First, you need to distinguish between the data the cells in your column are displaying, and the cells that actually display those data. The cellValueFactory determines the data that are displayed. The PropertyValueFactory is a cellValueFactory implementation that references a JavaFX Property, so when you call
storeNumberColumn.setCellValueFactory(new PropertyValueFactory<Store, String>("id"));
it effectively tells the cells in the storeNumberColumn to call the idProperty() method on the Store object in the current row to get the data for the cell. (If no such method exists, it will try to use getId() as a backup plan.)
By default, you get a cellFactory that displays text resulting from calling toString() on the data generated by the cellValueFactory. In the case where your data are simply Strings, this is usually what you need. In other cases, you often need to provide a cellFactory of your own to get the correct way to display the data.
In your case, the data for the commentColumn are simply the number of comments. You are going to display that by choosing an image based on that numeric value.
So you should have
TableColumn<Store, Number> commentColumn = new TableColumn<>("Comments");
For the cellValueFactory, you can just use
commentColumn.setCellValueFactory(new PropertyValueFactory<>("commentsCount"));
Then you need a cellFactory that displays the appropriate ImageView:
commentColumn.setCellFactory(new Callback<TableColumn<Store, Number>, new TableCell<Store, Number>>() {
#Override
public TableCell<Store, Number>() {
private ImageView imageView = new ImageView();
#Override
public void updateItem(Number numberOfComments, boolean empty) {
super.updateItem(count, empty) ;
if (empty) {
setGraphic(null);
} else {
if (numberOfComments.intValue() == 0) {
setGraphic(null);
} else if (numberOfComments.intValue() == 1) {
imageView.setImage(new Image(COMMENT_SINGLE_FLAG_SOURCE));
setGraphic(imageView);
} else {
imageView.setImage(new Image(COMMENT_DOUBLE_FLAG_SOURCE));
setGraphic(imageView);
}
}
}
}
});
The second issue is actually about the update. A TableView keeps its contents "live" by observing JavaFX properties that are provided by the cellValueFactory as ObservableValues. If the value might change while the table is displayed, you must provide an actual property that can be observed: using a ReadOnlyObjectWrapper is no good (because it's read only, so it's wrapped value will not change). The PropertyValueFactory will also return a ReadOnlyObjectWrapper if you do not have JavaFX property accessor methods (i.e. if it is only using getXXX() methods to access the data). So your model class must provide JavaFX Properties.
You can make an immediate fix to this by updating CommentEntity to use an IntegerProperty:
public abstract class CommentEntity {
private final IntegerProperty commentsCount = new SimpleIntegerProperty();
public final int getCommentsCount() {
return commentsCountProperty().get();
}
public final void setCommentsCount(int commentsCount) {
commentsCountProperty().set(commentsCount);
}
public IntegerProperty commensCountProperty() {
return commentsCount ;
}
public abstract String getCommentIdentifier();
}
I would also strongly recommend updating the Store class to use JavaFX Properties in a similar manner.
I am new in Java and i have a few questions for more advanced developers.
I have Swing-based GUI application in which I have several AbstractActions.
A large group of AbstractActions creates new tab based on JPanel. For example:
// opens "Documents" tab
documentsAction = new AbstractAction(DOCUMENTS) {
#Override
public void actionPerformed(ActionEvent e) {
try {
int index = getTabIndex(DOCUMENTS);
if (index >= 0) {
// Tab exists, just open it.
tabbedPane.setSelectedIndex(index);
} else {
// No tab. Create it and open
newCatalogTab(new DocumentService(), DOCUMENTS);
}
} catch (ServiceException ex) {
printError(ex.getMessage());
}
}
};
documentsItem.setAction(documentsAction);
Where getTabIndex is:
private int getTabIndex(String tabName) {
int result = -1;
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
if (tabName.equals(tabbedPane.getTitleAt(i))) {
result = i;
break;
}
}
return result;
}
and newCatalogTab is:
private void newCatalogTab(ICatalog service, String Name) throws ServiceException {
CatalogPanel panel = new CatalogPanel(service);
tabbedPane.add(Name, panel);
tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
checkTabs(); // changes activity of buttons like "edit" and "delete"
}
So, many AbstractAction do the similar work:
Create instance of class, that extends AbstractPanel;
Pass data access interface (DocumentService in example) to instance;
Create a new tab with instance.
Can I somehow template this if data access interfaces will use different POJO's?
Can I create Generic interface and use it?
Can you show me right direction for thinking?
Thanks for wasting your time.
There are no templates in Java, so there will be some code duplication in any case. However, you can cut some of the boilerplate code by using factories. For example:
interface CatalogFactory {
public ICatalog makeCatalog();
}
class DocumentServiceFactory implements CatalogFactory {
#Override
public ICatalog makeCatalog() {
return new DocumentService();
}
}
class TabAction extends AbstractAction {
private final String name;
private final CatalogFactory factory;
//Appropriate constructor...
#Override
public void actionPerformed(ActionEvent e) {
//...
newCatalogTab(factory.makeCatalog(), name);
//...
}
}
Then you can do
documentsItem.setAction(new TabAction(DOCUMENTS, new DocumentServiceFactory()));
without having to create a separate anonymous AbstractAction for each tab.
Similarly for panels and possibly other objects where this pattern fits.
I have an object, Supply, that can either be an ElecSupply or GasSupply (see related question).
Regardless of which subclass is being edited, they all have a list of BillingPeriods.
I now need to instantiate N number of BillingPeriodEditors based on the contents of that list, and am pretty baffled as to how I should do it.
I am using GWTP. Here is the code of the SupplyEditor I have just got working:
public class SupplyEditor extends Composite implements ValueAwareEditor<Supply>
{
private static SupplyEditorUiBinder uiBinder = GWT.create(SupplyEditorUiBinder.class);
interface SupplyEditorUiBinder extends UiBinder<Widget, SupplyEditor>
{
}
#Ignore
final ElecSupplyEditor elecSupplyEditor = new ElecSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor> elecSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor>(
elecSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof ElecSupply);
if(!(value instanceof ElecSupply))
{
showGasFields();
}
else
{
showElecFields();
}
}
};
#Ignore
final GasSupplyEditor gasSupplyEditor = new GasSupplyEditor();
#Path("")
final AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor> gasSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor>(
gasSupplyEditor)
{
#Override
public void setValue(final Supply value)
{
setValue(value, value instanceof GasSupply);
if(!(value instanceof GasSupply))
{
showElecFields();
}
else
{
showGasFields();
}
}
};
#UiField
Panel elecPanel, gasPanel, unitSection;
public SupplyEditor()
{
initWidget(uiBinder.createAndBindUi(this));
gasPanel.add(gasSupplyEditor);
elecPanel.add(elecSupplyEditor);
}
// functions to show and hide depending on which type...
#Override
public void setValue(Supply value)
{
if(value instanceof ElecSupply)
{
showElecFields();
}
else if(value instanceof GasSupply)
{
showGasFields();
}
else
{
showNeither();
}
}
}
Now, as the list of BillingPeriods is a part of any Supply, I presume the logic for this should be in the SupplyEditor.
I got some really good help on the thread How to access PresenterWidget fields when added dynamically, but that was before I had implemented the Editor Framework at all, so I think the logic is in the wrong places.
Any help greatly appreciated. I can post more code (Presenter and View) but I didn't want to make it too hard to read and all they do is get the Supply from the datastore and call edit() on the View.
I have had a look at some examples of ListEditor but I don't really get it!
You need a ListEditor
It depends of how you want to present them in your actual view, but the same idea apply:
public class BillingPeriodListEditor implements isEditor<ListEditor<BillingPeriod,BillingPeriodEditor>>, HasRequestContext{
private class BillingPeriodEditorSource extends EditorSource<BillingPeriodEditor>{
#Override
public EmailsItemEditor create(final int index) {
// called each time u add or retrive new object on the list
// of the #ManyToOne or #ManyToMany
}
#Override
public void dispose(EmailsItemEditor subEditor) {
// called each time you remove the object from the list
}
#Override
public void setIndex(EmailsItemEditor editor, int index) {
// i would suggest track the index of the subeditor.
}
}
private ListEditor<BillingPeriod, BillingPeriodEditor> listEditor = ListEditor.of(new BillingPeriodEditorSource ());
// on add new one ...
// apply or request factory
// you must implement the HasRequestContext to
// call the create.(Proxy.class)
public void createNewBillingPeriod(){
// create a new one then add to the list
listEditor.getList().add(...)
}
}
public class BillingPeriodEditor implements Editor<BillingPeriod>{
// edit you BillingPeriod object
}
Then in you actual editor edit as is in the path Example getBillingPeriods();
BillingPeriodListEditor billingPeriods = new BillingPeriodListEditor ();
// latter on the clickhandler
billingPeriods.createNewBillingPeriod()
You are done now.