0

I am trying to develop a login application which takes the username and password from the user and check with the database and if it's true then it changes to another view .if not it displays the alert. I tried doing a basic version where the password is stored in the text file and it checks in the controller class only. But I want to implement MVC architecture. I am unable to complete the model class and controller class for this. How can I do this?

View class:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.effect.DropShadow?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>

<AnchorPane nodeOrientation="LEFT_TO_RIGHT" prefHeight="720.0" prefWidth="1280.0" style="-fx-background-color: #ffffff;" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gload.views.main.LoginScreenctrl">
    <VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="720.0" prefWidth="1280.0" style="-fx-background-color: #F3F3F3;" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
        <AnchorPane minHeight="118.0" minWidth="0.0" prefHeight="150.0" prefWidth="1280.0">
            <HBox alignment="TOP_CENTER" layoutX="177.0" layoutY="30.0" prefHeight="90.0" prefWidth="860.0" AnchorPane.bottomAnchor="30.0" AnchorPane.leftAnchor="177.0" AnchorPane.rightAnchor="243.0" AnchorPane.topAnchor="30.0">
                <Text fx:id="gloadtext" fill="#00bbde" fontSmoothingType="LCD" stroke="WHITE" strokeType="OUTSIDE" styleClass="centered" text="GLOAD for" textAlignment="CENTER" wrappingWidth="401.13671875" HBox.hgrow="NEVER">
                    <font>
                        <Font name="Times New Roman Bold" size="68.0" />
                    </font>
                </Text>

                <ImageView fitHeight="75.0" fitWidth="417.0" pickOnBounds="true" preserveRatio="true">
                    <image>
                        <Image url="@../../../main/resources/Images/1200px-Logo.svg.png" />
                    </image>
                </ImageView>
            </HBox>
        </AnchorPane>

        <Separator nodeOrientation="LEFT_TO_RIGHT" prefHeight="23.0" prefWidth="1263.0" />

        <AnchorPane maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="528.0" prefWidth="1280.0" stylesheets="@../../../main/resources/style.css" VBox.vgrow="ALWAYS">
            <VBox alignment="TOP_CENTER" layoutX="275.0" layoutY="21.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="504.0" prefWidth="640.0" style="-fx-background-color: #ffffff;" stylesheets="@../../../main/resources/style.css" AnchorPane.leftAnchor="275.0" AnchorPane.topAnchor="21.0">
                <ImageView fitHeight="60.0" fitWidth="330.0" pickOnBounds="true" preserveRatio="true">
                    <image>
                        <Image url="@../../../main/resources/Images/1200px-Logo.svg.png" />
                    </image>

                    <VBox.margin>
                        <Insets top="10.0" />
                    </VBox.margin>
                </ImageView>

                <Text fill="#22286f" fontSmoothingType="LCD" strokeType="OUTSIDE" strokeWidth="0.0" text="Welcome to ****" textAlignment="CENTER">
                    <font>
                        <Font name="Impact" size="30.0" />
                    </font>
                </Text>

                <Text fontSmoothingType="LCD" strokeType="OUTSIDE" strokeWidth="0.0" text="Sign In to Continue" textAlignment="CENTER">
                    <font>
                        <Font name="Impact" size="22.0" />
                    </font>
                </Text>

                <AnchorPane prefHeight="344.0" prefWidth="640.0" style="-fx-background-color: #ffffff;">
                    <TextField fx:id="user" layoutX="199.0" layoutY="71.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="28.0" prefWidth="263.0" promptText="Username" AnchorPane.bottomAnchor="240.0" AnchorPane.leftAnchor="199.0" AnchorPane.rightAnchor="178.0" AnchorPane.topAnchor="71.0" />*

                    <PasswordField fx:id="password" layoutX="201.0" layoutY="145.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onKeyPressed="#enter" prefHeight="28.0" prefWidth="263.0" promptText="Password" AnchorPane.bottomAnchor="166.0" AnchorPane.leftAnchor="201.0" AnchorPane.rightAnchor="176.0" AnchorPane.topAnchor="145.0" />

                    <Text layoutX="199.0" layoutY="56.0" strokeType="OUTSIDE" strokeWidth="0.0" text="Username:" wrappingWidth="152.291015625" AnchorPane.bottomAnchor="282.1064453125" AnchorPane.leftAnchor="199.0" AnchorPane.rightAnchor="288.708984375" AnchorPane.topAnchor="39.9599609375">
                        <font>
                            <Font name="Times New Roman Bold" size="18.0" />
                        </font>
                    </Text>

                    <Text layoutX="198.0" layoutY="132.0" strokeType="OUTSIDE" strokeWidth="0.0" text="eDir password:" AnchorPane.bottomAnchor="206.1064453125" AnchorPane.leftAnchor="198.0" AnchorPane.rightAnchor="324.5078125" AnchorPane.topAnchor="115.9599609375">
                        <font>
                            <Font name="Times New Roman Bold" size="18.0" />
                        </font>
                    </Text>

                    <ButtonBar layoutX="209.0" layoutY="241.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="37.0" prefWidth="213.0" AnchorPane.bottomAnchor="64.0" AnchorPane.leftAnchor="209.0" AnchorPane.rightAnchor="218.0" AnchorPane.topAnchor="241.0">
                        <padding>
                            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                        </padding>

                        <opaqueInsets>
                            <Insets />
                        </opaqueInsets>

                        <buttons>
                            <Button fx:id="login" lineSpacing="10.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#makeLogin" prefHeight="35.0" prefWidth="197.0" text="Log In">
                                <padding>
                                    <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
                                </padding>
                            </Button>
                        </buttons>
                    </ButtonBar>
                </AnchorPane>

                <padding>
                    <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
                </padding>

                <effect>
                    <DropShadow blurType="GAUSSIAN" />
                </effect>
            </VBox>

            <VBox.margin>
                <Insets />
            </VBox.margin>
        </AnchorPane>
    </VBox>
</AnchorPane>

Controller Class:

package gload.views.main;

import gload.model.Login;
import gload.utils.SceneLoader;

import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.Scanner;

public class LoginScreenctrl implements Initializable {
    private Login model;

    public LoginScreenctrl(Login model) {
        this.model = model;
    }

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    public TextField user;

    @FXML
    private PasswordField password;

    @FXML
    private Button login;

    public TextField getUser() {
        return model.getUser();
    }

    public void setUser(TextField user) {
        model.setUser(user);
    }

    @FXML
    public PasswordField getPassword() {
        return model.getPassword();
    }

    public void setPassword(PasswordField password) {
        model.setPassword(password);
    }

    public boolean grantAccess;

    public void setGrantAccess(boolean grantAccess) {
        this.grantAccess = grantAccess;
    }

    public void enter(KeyEvent keyEvent) {                                                  
        // to login with enter button on password field
        if (keyEvent.getCode() == KeyCode.ENTER)
            login.fire();
    }

    @FXML
    public void makeLogin(ActionEvent  event) throws IOException {
        // This method gets triggered and check the value with the database to grant
        String username = user.getText();
        String pass = password.getText();
        File userdata = new File("users.txt"); // When Clicked on Login
        System.out.println("File Opened");
        try {
            Scanner read = new Scanner(userdata);
            int i = 0; // count lines in the file
            while (read.hasNextLine()) {
                i++;
                // loop through every line in the file and check against the user name & password (Saved in Pairs of lines)
                if(read.nextLine().equals(username)) { // if the same user name
                    System.out.println("Read " + i + " Line");
                    i++;
                    if(read.nextLine().equals(pass)) { // check password
                        setGrantAccess(true); // if also same, change boolean to true
                        System.out.println("Read " + i + " Line");
                        i++;
                        break; // and break the for-loop
                    }
                }
            }

            if (grantAccess) {
                // let the user continue
                // and do other stuff, for example: move to next window
                SceneLoader.loadscene("/gload/views/main/welcome.fxml",
                        (Stage)((Node) event.getSource()).getScene().getWindow());
                setUser(user);
                //new WelcomeCtrl().logas.setText("User logged in as "+ user);
                //new WelcomeCtrl().displayLogAs();
            } else {
                // return Alert message to notify the deny
                // display error
                Alert dialog = new Alert(Alert.AlertType.ERROR);
                dialog.setTitle("Not able to Login");
                dialog.setHeaderText("Incorrect Username or Password");
                dialog.setContentText("You have entered incorrect username or Password.\nPlease try Again");
                dialog.showAndWait();
            }

            read.close();
            System.out.println("File Closed");
        } catch (FileNotFoundException e) {
            System.out.println("Error : DATABASE ACCESS ERROR!");
            e.printStackTrace();
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        // Nothing
    }
}

Model Class:

package gload.model;

import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;

public class Login {
    private TextField user;
    private PasswordField password;

    public TextField getUser() {
        return user;
    }

    public void setUser(TextField user) {
        this.user = user;
    }

    public PasswordField getPassword() {
        return password;
    }

    public void setPassword(PasswordField password) {
        this.password = password;
    }

    public Login() {
        String username = user.getText();
        String pass = password.getText();
    }
}

Main Class:

package gload;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.util.Objects;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(Objects.requireNonNull(getClass().getResource("views/main/loginscreen.fxml")));
        primaryStage.setTitle("GLOAD");
        primaryStage.setScene(new Scene(root, 1280, 720));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
James_D
  • 201,275
  • 16
  • 291
  • 322
HJ3008
  • 25
  • 5
  • 3
    You should not store JavaFX components into your model. Instead of storing `TextField user` and `PasswordField password`, you should create `String user` and `String password`, then implement your logging mechanism inside the model. The login method would have this prototype: `public bool login(String usr, String pw)` and would check whether the given parameters match the model data. By the way, what is the interest for `getUser` and `getPassword` in your controller? – 0009laH Jun 04 '21 at 09:59
  • @0009laH I was trying to use `getUser` to get the Username to display on welcome screen as **User Logged in as "Username"**. Is it a wrong way ? – HJ3008 Jun 04 '21 at 11:25
  • Why not :) But you must work with strings instead of `TextField` and `PasswordField`. In a general way, creating getters and setters with JavaFX nodes is tricky because you must avoid [node inconsistency](https://bugs.openjdk.java.net/browse/JDK-8116739) which creates a runtime exception. In your model, you can either 1) use **data binding** for real-time processing (dynamic storage) 2) create an **observable/observer** mechanism to launch events when a value is updated 3) store data in "non-JavaFX" types (integers, lists, etc.) and add getters/setters (but it's not real-time processing). – 0009laH Jun 04 '21 at 12:27
  • 3
    See if https://stackoverflow.com/questions/14187963/passing-parameters-javafx-fxml and https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx help. – James_D Jun 04 '21 at 13:11
  • 2
    Additional related general info: [Applying MVC With JavaFx](https://stackoverflow.com/questions/23187932/mvc-with-javafx). – jewelsea Jun 04 '21 at 20:10

1 Answers1

0

MVC is easier to understand without the FXML stuff, which has a confusing "Controller" class of its own.

A Login Model:

public class LoginModel {

    private StringProperty user = new SimpleStringProperty("");
    private StringProperty password = new SimpleStringProperty("");

    public String getUser() {
        return user.get();
    }

    public StringProperty userProperty() {
        return user;
    }

    public void setUser(String user) {
        this.user.set(user);
    }

    public String getPassword() {
        return password.get();
    }

    public StringProperty passwordProperty() {
        return password;
    }

    public void setPassword(String password) {
        this.password.set(password);
    }
}

A Login Controller:

public class LoginController {

    private LoginViewBuilder viewBuilder;
    private LoginModel model = new LoginModel();
    private Stage stage;

    public LoginController(Stage stage) {
        this.stage = stage;
        viewBuilder = new LoginViewBuilder(model, () -> checkAccess());
    }

    public Region getView() {
        return viewBuilder.getView();
    }

    private void checkAccess() {
        if (model.getUser().equals("fred") && model.getPassword().equals("password")) {
            loadMainApplication();
        } else {
            displayErrorMessage();
        }
    }

    private void displayErrorMessage() {
        Alert dialog = new Alert(Alert.AlertType.ERROR);
        dialog.setTitle("Not able to Login");
        dialog.setHeaderText("Incorrect Username or Password");
        dialog.setContentText("You have entered incorrect username or Password.\nPlease try Again");
        dialog.showAndWait();
    }

    private void loadMainApplication() {
        VBox vBox = new VBox(new ImageView(new Image("/images/win.png")));
        stage.setScene(new Scene(vBox));
    }

}

Next, a Login ViewBuilder.

Personally, I think that FXML is a horrible waste of time, but if you insist on using it, then the call to the FMXL loader and the instantiation of the FXML Controller would go in the getView() method here. Of course, you'll need to find a way to pass a reference to the Model to the FXML Controller in order to bind the Model properties to the TextField/PasswordField controls.

If you do that, then you'll see that the FXML Controller is really just the View part of MVC - not the Controller:

public class LoginViewBuilder {

    private final LoginModel model;
    private Runnable actionRunnable;

    public LoginViewBuilder(LoginModel model, Runnable actionRunnable) {
        this.model = model;
        this.actionRunnable = actionRunnable;
    }

    public Region getView() {
        HBox userHBox = new HBox(10, new Text("User ID: "), createBoundTextField(model.userProperty()));
        HBox passwordHBox = new HBox(10, new Text("Password: "), createBoundPasswordField(model.passwordProperty()));
        Button loginButton = new Button("Login");
        loginButton.setOnAction(evt -> actionRunnable.run());
        VBox results = new VBox(10, userHBox, passwordHBox, loginButton);
        results.setAlignment(Pos.CENTER);
        return results;
    }

    private Node createBoundPasswordField(StringProperty boundProperty) {
        PasswordField results = new PasswordField();
        results.textProperty().bindBidirectional(boundProperty);
        return results;
    }

    private Node createBoundTextField(StringProperty boundProperty) {
        TextField results = new TextField("");
        results.textProperty().bindBidirectional(boundProperty);
        return results;
    }
}

And a Main:

public class LoginMain extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setScene(new Scene(new LoginController(primaryStage).getView()));
        primaryStage.setTitle("GLOAD");
        primaryStage.show();
    }
}
DaveB
  • 1,836
  • 1
  • 15
  • 13
  • 1
    _MVC is easier to understand without the FXML stuff_ might be, but using FXML is at the very center of OP's problem (modulo her mixing view into the model which is done correctly in the follow-up question ;) – kleopatra Jun 04 '21 at 14:59
  • I disagree, understanding of MVC is at the very centre of the question, FXML just obfuscates that. FXML causes so, so much confusion for JavaFX newbies. The use of the term "Controller", really messes a lot of people up. I've added a little bit about how you'd integrate FXML into the LoginViewBuilder to my answer, and you can see how little it would fundamentally change my answer. – DaveB Jun 04 '21 at 15:27
  • 1
    The FXML-"Controller" pattern is really an implementation of the MVP pattern (passive view variation); the "controller" should probably have been called a "Presenter". I'm not sure this adds anything beyond the question I linked under the OP; [this one](https://stackoverflow.com/questions/32342864/applying-mvc-with-javafx) and, linked from there, [this one](https://stackoverflow.com/questions/36868391/using-javafx-controller-without-fxml/36873768#36873768). – James_D Jun 04 '21 at 17:56
  • Thank you for the suggestion everyone. It helped me a lot to learn. – HJ3008 Jun 08 '21 at 07:28