Building a Vaadin web app with dynamic content - java
I'm trying to create a web app via Vaadin (Vaadin Framework 8).
I read several pages of documentation but still, I have big problems concerning the structure of a Vaadin app. My problem is that I lack something about the theory behind it, and I'll try to expose my problems with a first attempt of code. I'll really appreciate anything that can help me understand how Vaadin works.
I want to create a website where a user can register, log in and log out.
The structure of the GUI of the website is an Homepage where there is something like a button or something like that to do the login if the user is not logged, and if the user is logged, instead of the login button, it should appear a logout button.
First thing, I have a class that extends UI; in that class, I set the servlet and the init() method.
In the init() method, I start creating a VerticalLayout(), then a MenuBar and a Panel (called contentPanel). Then, I create a Navigator. The first problem I encounter is that I understand the Navigator as a possibility of browsing between different pages; the constructor of a Navigator wants a SingleComponentContainer, so for now, I don't know how to navigate between different web pages. For my example, in the constructor I use the Panel: new Navigator(this, contentPanel); Then I add different View that will then appear inside the panel. Finally, I navigate to the Welcome page.
MyUI class:
public class MyUI extends UI {
/**
* Class that checks if the user is in the database
* and the psw inserted is correct
*/
public static Authentication AUTH;
public static User user = null;
#WebServlet(value = "/*", asyncSupported= true)
#VaadinServletConfiguration(productionMode = false, ui = MyUI.class)
public static class MyUIServlet extends VaadinServlet {
}
#Override
protected void init(VaadinRequest request) {
AUTH = new Authentication();
VaadinSession.getCurrent().setAttribute("user", user);
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
Panel contentPanel = new Panel("Main Panel");
contentPanel.setSizeFull();
new Navigator(this, contentPanel);
getNavigator().addView(LoginPage.NAME, LoginPage.class);
getNavigator().setErrorView(LoginPage.class);
getNavigator().addView(LogoutPage.NAME, LogoutPage.class);
getNavigator().addView(WelcomePage.NAME, WelcomePage.class);
MenuBar.Command welcome = new Command() {
#Override
public void menuSelected(MenuItem selectedItem) {
getNavigator().navigateTo(WelcomePage.NAME);
}
};
MenuBar.Command login = new Command() {
#Override
public void menuSelected(MenuItem selectedItem) {
getNavigator().navigateTo(LoginPage.NAME);
}
};
MenuBar.Command logout = new Command() {
#Override
public void menuSelected(MenuItem selectedItem) {
getNavigator().navigateTo(LogoutPage.NAME);
}
};
MenuBar mainMenu = new MenuBar();
mainMenu.addItem("Welcome", VaadinIcons.ARROW_CIRCLE_LEFT, welcome);
mainMenu.addItem("Login", VaadinIcons.ENTER, login);
mainMenu.addItem("Logout", VaadinIcons.EXIT, logout);
layout.addComponent(mainMenu);
layout.addComponent(contentPanel);
getNavigator().navigateTo(WelcomePage.NAME);
}
}
LoginPage class:
public class LoginPage extends VerticalLayout implements View {
private static final long serialVersionUID = 1L;
public static final String NAME = "loginpage";
public LoginPage(){
Panel panel = new Panel("Login");
panel.setSizeUndefined();
addComponent(panel);
FormLayout content = new FormLayout();
TextField username = new TextField("Username");
content.addComponent(username);
PasswordField password = new PasswordField("Password");
content.addComponent(password);
Button send = new Button("Enter");
send.addClickListener(new Button.ClickListener() {
private static final long serialVersionUID = 1L;
public void buttonClick(ClickEvent event) {
//The authenticate method will returns
//true if the credentials are correct
//false otherwise
if(MyUI.AUTH.authenticate(username.getValue(), password.getValue())){
//In AUTH there is a User field called "user"
//User is a class that represents an user (so it has mail, psw, name etc)
VaadinSession.getCurrent().setAttribute("user", MyUI.AUTH.getUser());
}else{
Notification.show("Invalid credentials", Notification.Type.ERROR_MESSAGE);
}
}
});
content.addComponent(send);
content.setSizeUndefined();
content.setMargin(true);
panel.setContent(content);
setComponentAlignment(panel, Alignment.MIDDLE_CENTER);
}
}
Logout class has the same structure of Login class; there is a logout method:
private void doLogout() {
MyUI.AUTH.setUser(null);
VaadinSession.getCurrent().setAttribute("user", MyUI.AUTH.getUser());
getSession().close();
}
Another problem is: how can I dynamically add components in my layout, basing on the user status (logged or not?)
Next problem is: I didn't understand how to effectively do a logout.
I'll add complete code to facilitate any tests.
public class LogoutPage extends VerticalLayout implements View {
private static final long serialVersionUID = 1L;
public static final String NAME = "logoutpage";
public LogoutPage(){
Panel panel = new Panel("Logout");
panel.setSizeUndefined();
addComponent(panel);
Button logout = new Button("Logout");
logout.addClickListener(e -> doLogout());
addComponent(logout);
}
private void doLogout() {
MyUI.AUTH.setUser(null);
VaadinSession.getCurrent().setAttribute("user", MyUI.AUTH.getUser());
getSession().close();
}
}
______________________________________________________________________________
public class WelcomePage extends VerticalLayout implements View {
private static final long serialVersionUID = 1L;
public static final String NAME = "welcomepage";
public WelcomePage() {
setMargin(true);
setSpacing(true);
Label welcome = new Label("Welcome");
welcome.addStyleName("h1");
addComponent(welcome);
}
#Override
public void enter(ViewChangeEvent event) {
}
}
______________________________________________________________________________
public class Authentication {
private static User user = null;
//those fields should be in user; this is just a test
private String userID = "ID";
private String psw = "psw";
public Authentication() {
}
public void setUser(User user) {
Authentication.user = user;
}
public User getUser(){
return Authentication.user;
}
public Boolean authenticate(String userID, String psw){
if(userID == this.userID && psw == this.psw) {
user = new User();
return true;
}
return false;
}
}
I have seen many users struggling with the vaadin concept.
In short it is not a page-based framework, where you switch to a new page with each mouse click.
Instead it is more like a Swing application, where you have a single gui instance, where you add forms/poups/buttons, modify them and remove them based on user interaction. This is known as a Single-Page Application.
The Navigator is mainly used to allow the user to navigate with the backward/forward buttons of the webbrowser to the "previous" page, or bookmark specific pages.
This link provides some more detailed informations about this concept.
To answer you question:
- Use a single page/nvigation
- When not logged in, show a modal login popup
- When the user correctly enters authentification, remove the popup and show the main content in the vertical layout
- When the user logs out, remove the content from the vertical layout
For your simple use case, there is no need to work with the Navigator or Views
I initially ran into the same issues, which got me interested in Spring and ultimately Vaadin4Spring. Check out https://github.com/peholmst/vaadin4spring/tree/master/samples/security-sample-managed even if you have no interest in using Spring. It will provide you some insight on your View Navigation and with the addition of the Vaadin4Spring Sidebar it can make it easy to control access to views and menus. I am sure permissions will be your focus soon which can be complex.
Related
How do I force the view class to reload in Vaadin Flow when navigating to the same view
Assuming my view is: #Route(value="test") public class TestView extends VerticalLayout implements BeforeEnterObserver { public TestView() { super(); // do some stuff. new Button("Test", click -> getUI().ifPresent(ui -> ui.navigate("test"))); } #Override public void beforeEnter(BeforeEnterEvent event) { // do some other stuff. } } If I click on the Test button above then the constructor is not instantiated however the beforeEnter() method is called. In my case I would much prefer to instantiate the whole view class because the beforeEnter() method includes some logic and setup code that the view needs that if called twice can mess up the components. Specially the beforeEnter() does some checks and depending on the details may call different component rendering paths.
Please try the code below. It removes the view (navigation target) instance from UI and then calls page reload on the client side. Then, after page reload, the next request to server will force creating a new instance of the view. #Route(value="test") public class TestView extends VerticalLayout implements BeforeEnterObserver { private final String uuid; public TestView() { super(); // do some stuff. System.out.println("CTOR called"); uuid = UUID.randomUUID().toString(); final Element view = getElement(); Button button = new Button("Test", click -> { getUI().ifPresent(ui -> ui.getPage().setLocation("test")); view.removeFromParent(); }); add(button); } #Override public void beforeEnter(BeforeEnterEvent event) { // do some other stuff. System.out.println("Before enter"); System.out.println("UUID = " + uuid); } } Output: CTOR called Before enter UUID = 74306acc-3771-4998-aa46-19834ca9e033 CTOR called Before enter UUID = dbc1be2a-1bee-4da7-b676-187208621569 Hope this is something you wanted to reach.
If you are using Spring with Vaadin, you can annotate the class with #Component #Scope("prototype")
How to handle controller/views creation in a custom MVC
I'm on an application which should open a GUI specific commands. I tried to implement a sort of MVC like that (simplified version) : class View1 { // Delegate gui framework private Gui delegate; private Consumer<String> nextButtonCallback; private Model model; public void open() { // open the view here and populate it with model values // onButtonClick invoke nextButtonCallback.accept("data"); } } class EditController1 { private Model model; public void start() { } private void openView1() { View1 view = new View1(model); view.nextButtonCallback= string -> { // open a second view view.close(); openView2(); }; view.open(); } private void openView1() { View2 view = new View2(model); view.backButtonCallback = string -> { // go back view.close(); openView1(); }; view.open(); } } class Command { private ModelService modelService; public void onCommand1(String modelId) { Model model = modelService.getModel(modelId); EditController1 controller = new EditController1 (model); controller.start(); } } Right now the controller is controlling several views and handling internal navigation (is that a good idea ?) Now, lets say I have another controller (InfoController) which is opened by another command (infoCommand). I would like to open edition when clicking "edit" button. But since we are in the InfoController I can not open edit views. Same with "back" button, how to know if InfoController should open info view when EditController views are closed ? How should I handle navigation between internal and external views with this kind of controllers ? Or how should I rework to make it better ? :) Thanks
Is there a way to disable/enable elements in a javaFx application on the startup of said application, i.e Panes and buttons
Ok so this is the first question I've asked on StackOverflow so apologies if its unclear. Basically, I am making a program in JavaFx that is an ordering system for a fake Cafe. Its for an assignment and it really doesn't have to make conventional sense because my curriculum doesn't really care if it is actually useful or not, they just want to see you code some random stuff. Anyways, the problem i am having atm is that I am trying to make it so when I open the Main page called MainPage.fxml, 4 things will be pre-disabled/enabled. These elements are PinPane Which contains the sign-in buttons and labels), PrimaryPane (which contains all buttons leading to different ordering pages), SettingsBtn (Sends user to settings), and LogoutBtn (Self expanatory). This is important because when the program is first opened, MainPage is the first thing that is started. Once a user Signs in, and heads off to another page to select an item however, when they come back to the MainPage, where the current-order is displayed in PrimaryPage (I haven't actually done any code for that yet), I want to ensure that the disabled/enabled states of all 4 elements remains the same as when user left to go to another Page. Currently, I am using a static class called DataContainer.java, which contains all data shared by the program, and I thought i could put 3 boolean variables which basically just tell the program on the opening of MainPage what is disabled and enabled. However, my Primary problem is, I can't seem to be able to change the state of any of these elements on startup, and i have no idea how to do that other wise. My code for the MainPageController.java is below: ''' public class MainPageController { #FXML private Label Price; //fx:if -> Price #FXML public Pane PrimaryPage, PinPane; //fx:id -> PrimaryPage #FXML public Label Pin; //fx:id -> Pin #FXML public Button LogoutBtn, SettingsBtn; public void Check(ActionEvent event) throws IOException{ // This is the method I use to check the entered pin against current // saved pins. DataContainer.DataContainer(); // This is just a method i use for testing, it adds a manager account that i can sign in with // each time the program is opened because I haven't introduced account creation and saving // yet int pin = Integer.parseInt(Pin.getText()); int i = DataContainer.Users.size(); int x = 0; while (x <= i-1){ if (DataContainer.Users.get(x).PinNumber == pin){ // In this, once the pin is verified, each element is enabled and disabled, and the // boolean variables are set as well for future use System.out.println("test"); DataContainer.UserIndex = x; PrimaryPage.setDisable(false); LogoutBtn.setDisable(false); DataContainer.PrimaryPage = true; Pin.setText(""); PinPane.setDisable(true); DataContainer.PinPane = false; break; } x = x + 1; } if (DataContainer.Users.get(DataContainer.UserIndex).Position.equals("Manager")){ SettingsBtn.setDisable(false); DataContainer.SettingsBtn = true; } ''' This is the code for DataContainer.java ''' public class DataContainer{ public static void main(String args[]){ } public static void DataContainer(){ Users.add(owner); System.out.println("test"); } static boolean PinPane = true, PrimaryPage = false, SettingsBtn = false; // These boolean values are relevant to the MainPage application // Their purpose is to retain the information of the state in which the user // left the main page, i.e, if the PinPage is disabled, the PrimaryPage is enabled, etc. // this is important as if these variables don't exist the MainPage and its elements // go back to their default state and the user has to re-sign in. static String firstname, lastname, position; static int PinNo, PhoneNo, UserIndex; public static UserVariables user = new UserVariables(firstname, lastname, position, PinNo, PhoneNo); static UserVariables owner = new UserVariables("Test", "User", "Manager", 1234, 0434553); public static ArrayList<UserVariables> Users = new ArrayList<UserVariables>(); } ''' And finally this is the code for FInalIA.java (Main class): ''' public class FInalIA extends Application implements Serializable { public static void main(String args[]) { launch(args); } #Override public void start(Stage stage) throws Exception { // StackPane root = new StackPane(); Parent root = (Parent) FXMLLoader.load(getClass().getResource("MainPage.fxml")); Scene scene = new Scene(root); stage.setTitle("Main Page"); stage.setScene(scene); stage.setResizable(false); stage.show(); MainPageController.Open(); } } ''' And Finally, this is the code i was thinking of using, by making a public static method called 'Open()', and making all the panes static, and just just calling this method when ever MainPage is opened. ''' public static void Open(){ if (DataContainer.PinPane == false){ PinPane.setDisable(true); } else{ PinPane.setDisable(false); } if(DataContainer.PrimaryPage == false){ PrimaryPage.setDisable(true); LogoutBtn.setDisable(true); } else{ PrimaryPage.setDisable(false); LogoutBtn.setDisable(false); } if(DataContainer.SettingsBtn == false){ SettingsBtn.setDisable(true); } else{ SettingsBtn.setDisable(false); } } ''' Thanks to whoever helps me out with this (Also can you guys plz tell me if what i am writing is to non-concise and irrelevant or if its actually good)
Step one make Open non-static. You're going to create an instance of your controller and it will manage the associated items. public class MainPageController implements Initializable{ #FXML private Label price; //fx:if -> Price #FXML public pane primaryPage, pinPane; //fx:id -> PrimaryPage #FXML public Label pin; //fx:id -> Pin #FXML public button bogoutBtn, settingsBtn; public void Check(ActionEvent event) throws IOException{ //why is this method included but not open? } #Override public void initialize(URL url, ResourceBundle rb){ if (DataContainer.PinPane == false){ pinPane.setDisable(true); } else{ pinPane.setDisable(false); } primaryPage.setDisable( ! DataContainer.PrimaryPage ); logoutBtn.setDisable(! DataContainer.PrimaryPage); //etc etc } } I've made your controller implement Initializable, that way it has a method initialize that gets called when you start. I've also improved the naming, eg Pin should be named pin. If this doesn't work for you, I can replace this with a small enclosed example. You don't need to implement Initializable javafx will automatically call an appropriate initialize method.
JavaFX bind a hyperlink to a label
I am using MVP in my JavaFX application. Resources: public class InfoStageResources{ StringProperty lblBlogText; Hyperlink linkBlog; InfoStageResources() { this.lblBlogText = new SimpleStringProperty("link"); this.linkBlog = new Hyperlink("link"); } } Controller: public class InfoStageController{ private InfoStageView view; private InfoStageResources res; public void initView(){ this.res = new InfoStageResources(); this.view = new InfoStageView(this.res); this.initViewBindings(); } private void initViewBindings(){ this.view.lblBlog.textProperty().bind(this.res.lblBlogText); //this will not work this.view.lblBlog.textProperty().bind(this.res.linkBlog); } } View In my InfoStageView in just init my labels and style my view. How can bind my Hyperlink to my label. I tried some things but without success. My StringProperty lblBlogText isn't clickable but easy to bind. My goal: I want to open the browser with the link.
I think you are looking for this.view.lblBlog.textProperty().bind(this.res.linkBlog.textProperty());
The wicket Panel doesn't get refresh on button submit
I need to refresh the panel (it has been declared as panel in the below code) after I add a new group. The ItemSelectionComponent component is a different Panel that contains the added groups of a particular person. What I need to do is once I add a new group that particular panel (the ItemSelectionComponent panel with the wicket id "panel") should be refreshed and the newly added group should get displayed. I currently use target.addComponent(panel); to refresh, but it doesn't seems to be working :( Can someone tell me whats wrong? thanks!
Your avaiableGroups should be a LoadableDetachableModel that contains your list of GroupSelectionModels. When you use the AjaxSubmitLink get List from the LoadableDetachableModel and add to it. LoadableDetachableModel<List<GroupSelectionModel>> LDM = new LoadableDetachableModel<List<GroupSelectionModel>>() { private static final long serialVersionUID = 1L; #Override protected String load() { return ServiceLocator.getInstance().find(GroupService.class).getAllGroups();; } }; AjaxSubmitLink addBtn = new AjaxSubmitLink("addBtn") { #Override protected void onSubmit(AjaxRequestTarget target, Form<?> f) { List<Group> currentGroups = ServiceLocator.getInstance().find(GroupService.class).getAllGroups(); Group group = new Group(); group.setGroupType(Group.GroupType.EMAIL); group.setMerchant(merchant); group.setGroupName(form.getModelObject().getGroupName()); ServiceLocator.getInstance().find(GroupService.class).saveGroup(group); GroupSelectionModel newGroup = new GroupSelectionModel(); newGroup.setGroup(group); newGroup.setGroupSelected(true); LDM.getObject().add(newGroup); target.addComponent(panel); } }; Then pass LDM as a param to ItemSelectionComponent instead of avaiableGroups. Use LDM in ItemSelectionComponent like you did avaiableGroups. public class ItemSelectionComponent extends Panel{ private static final long serialVersionUID = 6670144847L; private LoadableDetachableModel<List<GroupSelectionModel>> model; public ItemSelectionComponent(String id,LoadableDetachableModel<List<GroupSelectionModel>> model){ super(id); this.model = model; init(); } private void init(){ WebMarkupContainer groupSelectionContainer = new WebMarkupContainer("groupSelectionContainer"); RepeatingView repeater = new RepeatingView("groupList"); WebMarkupContainer groupList; for(final GroupSelectionModel m : model.getObject()){ groupList = new WebMarkupContainer(repeater.newChildId()); WebMarkupContainer groupNameContainer = new WebMarkupContainer("groupNameContainer"); groupNameContainer.add(new Label("groupName", m.getGroup().getGroupName())); groupList.add(groupNameContainer); repeater.add(groupList); } groupSelectionContainer.add(repeater); this.add(groupSelectionContainer); } } Hope this helps.
I have one possible solution to your problem. Add a WebMarkupContainer: final WebMarkupContainer panelContainer = new WebMarkupContainer("panelContainer"); panelContainer.setOutputMarkupId(true); And in the html you will have it as: <div wicket:id="panelContainer"></div> Then you must add the panel to your markup container: panelContainer.add(panel) And add the markup container on the target instead of the panel: target.addComponent(panelContainer); If this doesn't work let me know and i will provide further assitance
When you target the panel the components in it will be refreshed. But what happens depends on how "ItemSelectionComponent" is using "avaiableGroups".