package com.example.application.navigation; import com.example.application.data.entity.Match; import com.example.application.data.entity.Matchday; import com.example.application.data.entity.Season; import com.example.application.data.service.MatchService; import com.example.application.data.service.MatchdayService; import com.example.application.data.service.SeasonService; import com.vaadin.flow.component.*; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.html.Label; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.select.Select; import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.HasUrlParameter; import com.vaadin.flow.router.WildcardParameter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; public class Navigation implements HasUrlParameter { // TODO: show dropdown menus also for invalid URLs (with content that fits the situation) private final String route; private final boolean onlyMatchdaysWithActivity; private final List runnablesToBeRunAfterSelection = new ArrayList<>(); private final SeasonService seasonService; private final MatchdayService matchdayService; private final MatchService matchService; private final List seasonList = new ArrayList<>(); private final List matchdayList = new ArrayList<>(); private final List matchList = new ArrayList<>(); private final Select seasonSelect = new Select<>(); private final Select matchdaySelect = new Select<>(); private final Select matchSelect = new Select<>(); private String seasonParam; private String matchdayParam; private String matchParam; private boolean autoselectSeason = false; private boolean autoselectMatchday = false; private boolean autoselectMatch = false; private final Label invalidUrlLabel = new Label(); private final Button prevMatchdayButton = new Button(new Icon(VaadinIcon.ARROW_LEFT)); private final Button nextMatchdayButton = new Button(new Icon(VaadinIcon.ARROW_RIGHT)); public Navigation(String route, @Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService, @Autowired MatchService matchService) { this(route, seasonService, matchdayService, matchService, false); } public Navigation(String route, @Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService, @Autowired MatchService matchService, boolean onlyMatchdaysWithActivity) { this.route = route; this.seasonService = seasonService; this.matchdayService = matchdayService; this.matchService = matchService; this.onlyMatchdaysWithActivity = onlyMatchdaysWithActivity; fillSeasonSelectWithData(); seasonSelect.addValueChangeListener(seasonSelectValueChangeListener()); matchdaySelect.addValueChangeListener(matchdaySelectValueChangeListener()); matchSelect.addValueChangeListener(matchSelectValueChangeListener()); } public void setAutoselectSeason(boolean autoselectSeason) { this.autoselectSeason = autoselectSeason; } public void setAutoselectMatchday(boolean autoselectMatchday) { this.autoselectMatchday = autoselectMatchday; } public void setAutoselectMatch(boolean autoselectMatch) { this.autoselectMatch = autoselectMatch; } // TODO: run the runnables after each selection (anyhow), then push history state (UI.getCurrent().getPage().getHistory().pushState(null, "http://host.com/person?action=edit&id=1");). // Navigate as little as possible. private void updateUrl() { String params = NavigationUtils.getWildcardParam(seasonParam, matchdayParam, matchParam); UI.getCurrent().getPage().getHistory().pushState(null, String.format("%s/%s", route, params)); } private HasValue.ValueChangeListener, Matchday>> matchdaySelectValueChangeListener() { return matchdayChangeEvent -> { Matchday matchday = matchdayChangeEvent.getValue(); if (matchday != null) { matchParam = matchday.toString(); updateUrl(); runnablesToBeRunAfterSelection.forEach(Runnable::run); } }; } private HasValue.ValueChangeListener, Season>> seasonSelectValueChangeListener() { return seasonChangeEvent -> { Season newSeason = seasonChangeEvent.getValue(); if (newSeason != null) { String seasonParam = newSeason.toString(); String matchdayParam = null; Matchday matchdayInNewSeason = null; Matchday matchdayInOldSeason = matchdaySelect.getValue(); if (matchdayInOldSeason != null) { matchdayParam = matchdayInOldSeason.toString(); matchdayInNewSeason = getMatchdayFromParam(matchdayParam, newSeason); } matchdayParam = matchdayInNewSeason == null ? "1" : matchdayParam; this.seasonParam = seasonParam; this.matchdayParam = matchdayParam; this.matchParam = null; updateUrl(); runnablesToBeRunAfterSelection.forEach(Runnable::run); } }; } private HasValue.ValueChangeListener, Match>> matchSelectValueChangeListener() { return matchChangeEvent -> { Match match = matchChangeEvent.getValue(); if (match != null) { matchParam = match.toString(); updateUrl(); runnablesToBeRunAfterSelection.forEach(Runnable::run); // TODO: offer different lists for season, matchday, match } }; } private void fillSeasonSelectWithData() { seasonList.clear(); seasonList.addAll(seasonService.getAllSeasonsSorted()); seasonSelect.setItems(seasonList); } private void fillMatchdaySelectWithData(Season season) { matchdayList.clear(); List matchdaysToAdd = onlyMatchdaysWithActivity ? matchdayService.getMatchdaysWithActivitySorted(season) : matchdayService.getMatchdaysSorted(season); matchdayList.addAll(matchdaysToAdd); matchdaySelect.setItems(matchdayList); } private void fillMatchSelectWithData(Matchday matchday) { matchList.clear(); matchList.addAll(matchService.getMatches(matchday)); matchSelect.setItems(matchList); } private boolean isMatchDayParamValid(@NonNull String matchdayParam) { return matchdayList.stream().anyMatch(matchday -> matchdayParam.equals(matchday.toString())); } private void navigate(String seasonParam, String matchdayParam, String matchParam) { // TODO: change this to String... -> see where you need which parameters UI.getCurrent().navigate(String.format("%s/%s", route, NavigationUtils.getWildcardParam(seasonParam, matchdayParam, matchParam))); } @Override public void setParameter(BeforeEvent event, @WildcardParameter String param) { Map map = NavigationUtils.getParameterMap(param); setParameter(map.get(UrlParameterType.SEASON), map.get(UrlParameterType.MATCHDAY), map.get(UrlParameterType.MATCH)); } private boolean paramInvalid(@Nullable String param) { return param == null || param.equals(""); } private void noMatchFound(Season season, Matchday matchday) { invalidUrlLabel.setText(String.format("No Match found in Matchday %s in Season %s!", matchday.toString(), season.toString())); } private void matchFound(Season season, Matchday matchday, Match match) { matchSelect.setValue(match); navigate(season.toString(), matchday.toString(), match.toString()); } private void noMatchdayFound(Season season) { invalidUrlLabel.setText(String.format("No Matchday found in Season %s!", season.toString())); } private void autoselectMatch(Season season, Matchday matchday) { Optional firstMatch = matchService.getFirstMatch(matchday); firstMatch.ifPresentOrElse( match -> matchFound(season, matchday, match), () -> noMatchFound(season, matchday)); } private void matchdayFound(Season season, Matchday matchday, String matchParam) { matchdaySelect.setValue(matchday); fillMatchSelectWithData(matchday); if (paramInvalid(matchParam) && autoselectMatch) { autoselectMatch(season, matchday); return; } navigate(season.toString(), matchday.toString(), matchParam); } private void noSeasonFound() { invalidUrlLabel.setText("No Season found!"); } private void autoselectMatchday(Season season, String matchParam) { Optional latestMatchday = matchdayService.getLastMatchdayWithActivityOrElseFirstMatchday(season); latestMatchday.ifPresentOrElse( matchday -> matchdayFound(season, matchday, matchParam), () -> noMatchdayFound(season)); } private void seasonFound(Season season, String matchdayParam, String matchParam) { seasonSelect.setValue(season); fillMatchdaySelectWithData(season); if (paramInvalid(matchdayParam) && autoselectMatchday) { autoselectMatchday(season, matchParam); return; } navigate(season.toString(), matchdayParam, matchParam); } private void autoselectSeason(String matchdayParam, String matchParam) { Optional latestSeason = seasonService.getLatestSeason(); latestSeason.ifPresentOrElse( season -> seasonFound(season, matchdayParam, matchParam), this::noSeasonFound); } private boolean autoselectIfNecessary(String seasonParam, String matchdayParam, String matchParam) { if (paramInvalid(seasonParam) && autoselectSeason) { autoselectSeason(matchdayParam, matchParam); return true; } if (paramInvalid(matchdayParam) && autoselectMatchday) { autoselectMatchday(seasonSelect.getValue(), matchParam); return true; } if (paramInvalid(matchParam) && autoselectMatch) { autoselectMatch(seasonSelect.getValue(), matchdaySelect.getValue()); return true; } return false; } public void setParameter(String seasonParam, String matchdayParam, String matchParam) { if (autoselectIfNecessary(seasonParam, matchdayParam, matchParam)) { for (Runnable runnable : runnablesToBeRunAfterSelection) { runnable.run(); } return; } Season season = getSeasonFromParam(seasonParam); if (season != null) { seasonSelect.setValue(season); this.seasonParam = seasonParam; fillMatchdaySelectWithData(season); } else if (autoselectSeason) { invalidUrlLabel.setText(String.format("Invalid URL! Season \"%s\" does not exist in the database!", seasonParam)); return; } Matchday matchday = getMatchdayFromParam(matchdayParam); if (matchday != null) { matchdaySelect.setValue(matchday); this.matchdayParam = matchdayParam; fillMatchSelectWithData(matchday); configureButtons(); } else if (autoselectMatchday) { String messageExtra = onlyMatchdaysWithActivity ? " or has no games played yet" : ""; invalidUrlLabel.setText(String.format("Invalid URL! Matchday \"%s\" in Season \"%s\" does not exist in the database%s!", matchdayParam, seasonParam, messageExtra)); return; } Match match = getMatchFromParam(matchParam); if (match != null) { matchSelect.setValue(match); this.matchParam = matchParam; } else if (autoselectMatch) { invalidUrlLabel.setText(String.format("Invalid URL: Match \"%s\" in Matchday \"%s\" in Season \"%s\" does not exist in the database!", matchParam, matchdayParam, seasonParam)); } for (Runnable runnable : runnablesToBeRunAfterSelection) { runnable.run(); } } @Nullable private Season getSeasonFromParam(@Nullable String seasonParam) { if (seasonParam == null) { return null; } for (Season season : seasonList) { if (seasonParam.equals(season.toString())) { return season; } } return null; } @Nullable private Matchday getMatchdayFromParam(@Nullable String matchdayParam) { return getMatchdayFromParam(matchdayParam, null); } @Nullable private Matchday getMatchdayFromParam(@Nullable String matchdayParam, @Nullable Season season) { if (matchdayParam == null) { return null; } List matchdayList = season == null ? this.matchdayList : matchdayService.getMatchdaysSorted(season); for (Matchday matchday : matchdayList) { if (matchdayParam.equals(matchday.toString())) { return matchday; } } return null; } @Nullable private Match getMatchFromParam(@Nullable String matchParam) { if (matchParam == null) { return null; } for (Match match : matchList) { if (matchParam.equals(match.toString())) { return match; } } return null; } public void addRunnableToBeRunAfterSelection(Runnable runnable) { runnablesToBeRunAfterSelection.add(runnable); } private void configureButtons() { prevMatchdayButton.setEnabled(isMatchDayParamValid(getPrevMatchdayParam())); prevMatchdayButton.addClickListener(getButtonClickListener(getPrevMatchdayParam())); nextMatchdayButton.setEnabled(isMatchDayParamValid(getNextMatchdayParam())); nextMatchdayButton.addClickListener(getButtonClickListener(getNextMatchdayParam())); } private ComponentEventListener> getButtonClickListener(String matchdayParam) { return buttonClickEvent -> navigate(seasonParam, matchdayParam, matchParam); } private String getPrevMatchdayParam() { try { return String.valueOf(Integer.parseInt(matchdayParam) - 1); } catch (NumberFormatException e) { return ""; } } private String getNextMatchdayParam() { try { return String.valueOf(Integer.parseInt(matchdayParam) + 1); } catch (NumberFormatException e) { return ""; } } public Optional getSelectedMatchday() { return matchdaySelect.getOptionalValue(); } public Optional getSelectedSeason() { return seasonSelect.getOptionalValue(); } public Optional getSelectedMatch() { return matchSelect.getOptionalValue(); } public Button getPrevMatchdayButton() { return prevMatchdayButton; } public Button getNextMatchdayButton() { return nextMatchdayButton; } public Label getInvalidUrlLabel() { return invalidUrlLabel; } public Select getSeasonSelect() { return seasonSelect; } public Select getMatchdaySelect() { return matchdaySelect; } public Select getMatchSelect() { return matchSelect; } public void selectMatch(Match match) { navigate(seasonParam, matchdayParam, match.toString()); } }