-
Notifications
You must be signed in to change notification settings - Fork 1
Client
MVP is a design pattern that separated the logic of a user interface from the business logic. It consists of three main components:
- Model: represents the business objects.
- View: responsible for displaying data and handling user interactions. Views have no notion of the model.
- Presenter: acts as a bridge between the Model and the View. It handles all of the logic for our application.
In our application has been used a built-in framework for browser history management: the Activity and Places framework.
The presenters in our app will be represented by activities
and they will have the purpose of containing both the business logic and a set of life cycles of each view. A place
is a Java object representing a particular state or location within the application. A Place can be converted to and from a URL history token.
The PlaceController
is crucial component for Activities and Places framework. It is responsible for coordinating the navigation between different places in the application. It does this by updating the browser's URL and the browser's history stack, so that the user can use the browser's back and forward buttons to navigate through the app.
The PlaceController also acts as a bridge between the places and the activities. When a user navigates to a new place, the PlaceController will call the appropriate activity to handle the transition to the new place. It also updates the current place and activity, so that the application can keep track of the user's current location.
// todo: spiegare molto meglio la parte del routing, è una parte fondamentale del nostro progetto
-
The HomeActivity class serves as the presenter for the HomeView, which is responsible for handling the logic of how the data should be displayed, it receives the data from the service class and pass it to the view to be displayed on the UI. The
fetchGameCards
method fetches the card data for a specific game from the server and passes it to the view withsetData
. -
The HomeViewImpl class is the concrete implementation of the HomeView that is responsible for displaying the data on the UI using GWT widgets. It also has the method that interact with the presenter, such as
onValueChanged
that handles the radio buttons events. ThesetData
takes the data and displays the data on UI.
// Model: base CardDecorator and concrete decorators for extra behaviors
abstract class CardDecorator {
CardImpl wrappee;
protected CardDecorator(CardImpl card) {
wrappee = card;
}
// extra behaviors
}
// Presenter: HomeActivity
class HomeActivity extends AbstractActivity implements HomeView.Presenter {
private final HomeView view;
private final CardServiceAsync rpcService;
public HomeActivity(HomeView view, CardServiceAsync rpcService) {
this.view = view;
this.rpcService = rpcService;
}
public void fetchGameCards(Game game) {
if (game == null) {
throw new IllegalArgumentException("game cannot be null");
}
rpcService.getGameCards(game, new BaseAsyncCallback<List<CardDecorator>>() {
@Override
public void onSuccess(List<CardDecorator> result) {
view.setData(result);
}
});
}
}
// View: HomeViewImpl
public class HomeViewImpl implements HomeView {
void setData(List<CardDecorator> data) {
// show the data
}
void onValueChanged(ValueChangeEvent<Boolean> e, Game game) {
presenter.fetchGameCards(game);
}
}
This class is a visual component that displays a preview of a selected game card with all its details on the Home Page. The widget accepts a "CardDecorator" object as input, which could be of any of the three types of cards (YuGi-Oh, Magic, Pokemon). To display the details of the card, the code uses three switch statements to build a "details" string containing the specific fields for each card type. This way, the class can handle all types of card and show their specific details.
public CardWidget(FunctionInterface parent, CardDecorator card) {
initWidget(uiBinder.createAndBindUi(this));
nameDiv.setInnerHTML(card.getName());
descDiv.setInnerHTML(card.getDescription());
typeDiv.setInnerHTML(card.getType());
if (card instanceof YuGiOhCardDecorator) {
imageDiv.setUrl(((YuGiOhCardDecorator) card).getSmallImageUrl());
// builds details string for YuGiOhCardDecorator
}
if (card instanceof PokemonCardDecorator) {
// builds details string for PokemonCardDecorator
}
if (card instanceof MagicCardDecorator) {
// builds details string for MagicCardDecorator
}
details.setInnerHTML(html);
detailsButton.addClickHandler(clickEvent -> parent.handleClickCard());
}
The homepage contains also GameFiltersWidget
, that is a widget that allows filtering a list of cards for a specific game. When a different game is selected, the options available for the filters are changed. For example, if Magic game is selected, options like "Name", "Artist", and "Description" will be displayed. There are also options to filter by special attributes and type. Additionally, there are checkboxes to filter by boolean fields like "hasFoil" or "isPromo".
The method filterGameCards
in the HomePresenter
class is responsible for filtering a list of "CardDecorator" objects based on various input parameters. These parameters include a special attribute value, a type value, a text input name, a text input value, a list of boolean input names, and a list of boolean input values.
The method iterates through each "CardDecorator" object in the list and checks if it should be included in the filtered list using a series of if/else statements.
The input parameters are used as follows:
- The "specialAttributeValue" is used to filter the cards based on a specific attribute, such as rarity for Magic cards or edition for Pokemon cards.
- The "typeValue" is used to filter the cards based on the type of card, such as Magic or Pokemon.
- The "textInputName" is used to specify which text field to search for the "textInputValue" in, such as name, description, or artist.
- The "textInputValue" is used to search for a specific value in the text field specified by "textInputName"
- The "booleanInputNames" and "booleanInputValues" are used together to filter the cards based on specific boolean attributes, such as "isFirstEdition" or "hasFoil"
If a card meets all the specified criteria, it is added to the "filteredCards" list and returned at the end of the method.
public List<CardDecorator> filterGameCards(String specialAttributeValue, String typeValue, String textInputName, String textInputValue,
List<String> booleanInputNames, List<Boolean> booleanInputValues) {
List<CardDecorator> filteredCards = new ArrayList<>();
for (CardDecorator card : cards) {
boolean shouldSkip = false;
if (!textInputValue.isEmpty()) {
// logic for filter for textAttribute
// pseudocode
if (card not meet the textAttribute criteria) {
shouldSkip = true;
}
}
if (!specialAttributeValue.equals("all")) {
// logic for filter for specialAttribute
// pseudocode
if (card not meet the specialAttribute criteria) {
shouldSkip = true;
}
}
if (!typeValue.equals("all")) {
// logic for filter for typeAttribute
// pseudocode
if (card not meet the typeAttribute criteria) {
shouldSkip = true;
}
}
if (!(booleanInputNames.isEmpty() && booleanInputValues.isEmpty())) {
// logic for filter for
// pseudocode
if (card not meet the booleanAttributes criteria) {
shouldSkip = true;
}
}
if (!shouldSkip) {
filteredCards.add(card);
}
}
return filteredCards;
}
-
The AuthenticationViewImpl class is the concrete implementation of the AuthenticationView. This view aim is to display an UI into which the visitor is able to create his own account and the user can authenticate himself. This is possible by switching the mode from
Login
toSignup
through theSwitch to mode
button. This class also has a ClickHandler associated with the Login/Signup button, calledauthButton
, which interacts with theauthenticate
method of the presenter using the enum variableAuthMode
. Moreover, theresetFields
method resets the email and password fields. -
The AuthenticationActivity class, instead, works as presenter for the AuthenticationView, which responsability is handling the logic when pressing the
Login
or theSignup
button. Theauthenticate
method makes the user navigate to the HomePage, with theplaceController
, if there's no error in the fields, or warn the user if an error occurres. In both cases, fields are reset. In fact, theonStop
method, callsresetFields
when the Activity stops, so when the user leaves the authentication page.
// Enum variable: AuthMode
public enum AuthMode {
Login,
Signup
}
// View: AuthenticationViewImpl
public class AuthenticationViewImpl implements AuthenticationView {
public AuthenticationViewImpl() {
authButton.addClickHandler((ClickEvent) -> {
if (authButton.getText().equals(AuthMode.Login.name())) {
presenter.authenticate(AuthMode.Login, emailField.getText(), passwordField.getText());
} else {
presenter.authenticate(AuthMode.Signup, emailField.getText(), passwordField.getText());
}
});
}
public void resetFields() {
emailField.setText("");
passwordField.setText("");
}
}
// Presenter: AuthenticationActivity
public class AuthenticationActivity extends AbstractActivity implements AuthenticationView.Presenter {
private final AuthenticationView view;
private final PlaceController placeController;
public AuthenticationActivity(AuthenticationView view, PlaceController placeController) {
this.view = view;
this.placeController = placeController;
}
public void onStop() {
view.resetFields();
}
public void authenticate(AuthMode authMode, String email, String password) {
try {
if (authMode == AuthMode.Login) {
// call signin and catch errors
} else if (authMode == AuthMode.Signup) {
// call signup and catch errors
}
goTo(new HomePlace());
} catch (Exception e) {
view.resetFields();
}
}
// placeController
public void goTo(Place place) {
placeController.goTo(place);
}
}
This page, besides reporting the information of the selected card, gives the logged-in user the possibility to add the card to the owned cards deck or to the wished cards deck and shows the lists of users who own it and who wish it. So the page is divided in three maining parts:
- The card information, which is populated by the CardActivity presenter through the method
fetchCard
. This method is responsible of making a call to the server to take the selected card through the game and cardId parameters passed in the place, and then to recall the methodsetData
of CardView. So the setData method deals with taking the result, of CardDecorator type, and printing in the interface the information related to each different type of card, ie type MagicCardDecorator, PokemonCardDecorator or YuGiHoCardDecorator.
- The "add to deck" section, which is implemented like a Widget named
AddCardToDeckWidget
that simply consists of a select, which allows the user to select the deck (Owned or Wished), and of a PushButton, which actually performs the addition. By clicking on the 'Add to deck' button a modal (AddCardToDeckModalWidget
) opens in order to let the user insert the properties of the physical card (status and description). Then, this values are sent back to CardView, which handles them and invokes the 'addCardToDeck' method of the presenter CardActivity sending the deck name parameter too. In the end, the presenter is responsible for invoking theaddPhysicalCardToDeck
method fromdeckService
passing all the requested parameters.
- The users lists, of which one is for the users who own the card (Owned by), and the other is for the users who instead wish it (Wished by). This lists are widgets (
UserListWidget
) and are created dynamically using GWT FlexTable. Moreover, using this lists, it is possible to propose an exchange to another user.
In conclusion, all the action buttons (the 'Add to deck' button and the 'Exchange' button) in this page are not showen to the visitors, while are showen to the logged-in users. This is possible thanks to the createUserWidgets
method of the view, which checks if the user is logged-in through the boolean variable isLoggedIn, and the presenter method update
with the help of the AuthSubject.
// Presenter: CardActivity
public class CardActivity extends AbstractActivity implements CardView.Presenter, Observer {
private final CardPlace place;
private final CardView view;
private final CardServiceAsync rpcService;
private final AuthSubject authSubject;
public CardActivity(CardPlace place, CardView view, CardServiceAsync rpcService, AuthSubject authSubject) {
this.place = place;
this.view = view;
this.rpcService = rpcService;
this.authSubject = authSubject;
authSubject.attach(this);
}
public void update() {
view.createUserWidgets(authSubject.isLoggedIn());
}
public void fetchCard() {
rpcService.getGameCard(place.getGame(), place.getCardId(), new BaseAsyncCallback<CardDecorator>() {
@Override
public void onSuccess(CardDecorator result) {
view.setData(result);
}
});
}
public void addCardToDeck(String deckName, String status, String description) {
deckService.addPhysicalCardToDeck(
authSubject.getToken(),
place.getGame(),
deckName,
place.getCardId(),
Status.getStatus(Integer.parseInt(status)),
description,
new AsyncCallback<Boolean>() {
@Override
public void onFailure(Throwable caught) {
// displayErrorAlert method of the view;
}
}
@Override
public void onSuccess(Boolean result) {
// displaySuccessAlert method of the view
}
}
);
}
}
// View: CardViewImpl
public class CardViewImpl extends Composite implements CardView {
private static final CardsViewImplUIBinder uiBinder = GWT.create(CardsViewImplUIBinder.class);
@UiField
HTMLPanel addCardToDeckContainer;
@UiField
HTMLPanel userLists;
Presenter presenter;
public void setData(CardDecorator data) {
String gameType = "";
// other properties declarations
if (data instanceof MagicCardDecorator) {
gameType = "Magic";
// properties checks
} else if (data instanceof PokemonCardDecorator) {
gameType = "Pokemon";
// properties checks
} else if (data instanceof YuGiOhCardDecorator) {
gameType = "YuGiOh";
// properties checks
}
// properties assignment to UI
}
public void createUserWidgets(boolean isLoggedIn) {
addCardToDeckContainer.clear();
userLists.clear();
// create AddCartToDeckWidget
if (isLoggedIn) addCardToDeckContainer.add(new AddCardToDeckWidget());
// create UserListWidget 'Exchange' buttons
userLists.add(new UserListWidget(
"Owned by",
cardHoldersUsers,
isLoggedIn
));
userLists.add(new UserListWidget(
"Wished by",
cardSeekerUsers,
isLoggedIn));
}
}