1 year ago

#339174

test-img

WeekendJedi

Grid not updating after entity attributes are changed

In my project, I have "Events" and "Users". Users sign up for events. Anyways the Events entity has a "participants" attribute which is an int that is the total capacity of the event, that is supposed to decrement every time a User signs up for the Event. Each Event also has an ArrayList attribute called "registrants" that is supposed to contain all the current User entities that have signed up for the Event.

My issue is that when a User signs up for an Event, this capacity attribute "participants" does not decrease at all like it is supposed to, which I suspect means the ArrayList "registrants" is also not be updated with the User that signs up. I re-check the EventManagementView after using a User to sign up for an event to see if the capacity is ever dropped from its initial set value, but it never is. Here's all the code that is directly used with this issue:

Events.java class for Events entity:

package ymca.tracker.application.data.entity;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;

import ymca.tracker.application.data.AbstractEntity;

@Embeddable
@Entity
public class Events extends AbstractEntity{

    private String name;
    private java.time.LocalDate startDate;
    private java.time.LocalDate endDate;
    private java.time.LocalTime startTime;
    private java.time.LocalTime endTime;
    private String recurring;
    // The capacity attribute I was referring to above
    public int participants;
    private String nonMemberPrice;
    private String memberPrice;
    private String location;
    private String description;
    // Registrants ArrayList that is supposed to hold all the User entities that sign up for an event
    @ElementCollection
    @OneToMany(fetch = FetchType.EAGER)
    public List<User> registrants = new ArrayList<User>();

    public Events() {

    }

    public Events(String name, LocalDate startDate, LocalDate endDate, LocalTime startTime, LocalTime endTime, 
        String recurring, int participants, String nonMemberPrice, String memberPrice, String location, 
            String description, ArrayList<User> registrants) {
            this.name = name;
            this.startDate = startDate;
            this.endDate = endDate;
            this.startTime = startTime;
            this.endTime = endTime;
            this.recurring = recurring;
            this.participants = participants;
            this.nonMemberPrice = nonMemberPrice;
            this.memberPrice = memberPrice;
            this.location = location;
            this.description = description;
            this.registrants = registrants;
        }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public java.time.LocalDate getStartDate() {
        return startDate;
    }
    public void setStartDate(java.time.LocalDate startDate) {
        this.startDate = startDate;
    }
    public java.time.LocalDate getEndDate() {
        return endDate;
    }
    public void setEndDate(java.time.LocalDate endDate) {
        this.endDate = endDate;
    }
    public java.time.LocalTime getStartTime() {
        return startTime;
    }
    public void setStartTime(java.time.LocalTime startTime) {
        this.startTime = startTime;
    }
    public java.time.LocalTime getEndTime() {
        return endTime;
    }
    public void setEndTime(java.time.LocalTime endTime) {
        this.endTime = endTime;
    }
    public String getRecurring() {
        return recurring;
    }
    public void setRecurring(String recurring) {
        this.recurring = recurring;
    }
    public int getParticipants() {
        return participants;
    }
    public void setParticipants(int participants) {
        this.participants = participants;
    }
    // This method is called (only with value 1 for now) to decrease participants count
    public void decreaseCapacity(int numToDecreaseBy) {
        this.participants = this.participants - numToDecreaseBy;
    }
    public String getMemberPrice() {
        return memberPrice;
    }
    public void setMemberPrice(String memberPrice) {
        this.memberPrice = memberPrice;
    }
    public String getNonMemberPrice() {
        return nonMemberPrice;
    }
    public void setNonMemberPrice(String nonMemberPrice) {
        this.nonMemberPrice = nonMemberPrice;
    }
    public String getLocation() {
        return location;
    }
    public void setLocation(String location) {
        this.location = location;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public List<User> getRegistrants() {
        return registrants;
    }
    public void setGuests(ArrayList<User> registrants) {
        this.registrants = registrants;
    }
    // This method is called to add a User to the registrants ArrayList
    public void addRegistrant(User user) {
        this.registrants.add(user);
    }
    public void removeRegistrant(User user) {
        this.registrants.remove(user);
    }
}

User.java class for User entity:

package ymca.tracker.application.data.entity;

import javax.persistence.Embeddable;
import javax.persistence.Entity;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;

import ymca.tracker.application.data.AbstractEntity;

@Embeddable
@Entity
public class User extends AbstractEntity {

    private boolean familyAccount;
    private String firstName;
    private String lastName;
    private String username;
    private String password;
    private String passwordSalt;
    private String passwordHash;
    private ymca.tracker.application.data.entity.Role role; 

    public User() {
        
    }

    public User(boolean familyAccount, String firstName, String lastName, 
        String username, String password, ymca.tracker.application.data.entity.Role role) {
        this.familyAccount = familyAccount;
        this.firstName = firstName;
        this.lastName = lastName;
        this.username = username;
        this.role = role;
        this.password = password;
        this.passwordSalt = RandomStringUtils.random(32);
        this.passwordHash = DigestUtils.sha1Hex(password + passwordSalt);
    }

    public boolean checkPassword(String password) {
        return DigestUtils.sha1Hex(password + passwordSalt).equals(passwordHash);
    }
    public boolean getFamilyAccount() {
        return familyAccount;
    }
    public void setFamilyAccount(boolean familyAccount) {
        this.familyAccount = familyAccount;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getPasswordSalt() {
        return passwordSalt;
    }
    public void setPasswordSalt(String passwordSalt) {
        this.passwordSalt = passwordSalt;
    }
    public String getPasswordHash() {
        return passwordHash;
    }
    public void setPasswordHash(String passwordHash) {
        this.passwordHash = passwordHash;
    }
    public ymca.tracker.application.data.entity.Role getRole() {
        return role;
    }
    public void setRole(ymca.tracker.application.data.entity.Role role) {
        this.role = role;
    }
}

My Staff/Admin account uses this EventManagementView below to initially create the event:

package ymca.tracker.application.views.eventmanagement;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.splitlayout.SplitLayout;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.timepicker.TimePicker;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;

import ymca.tracker.application.data.entity.Events;
import ymca.tracker.application.data.entity.User;
import ymca.tracker.application.data.service.EventsService;

@PageTitle("Event Management")
@Route(value = "event-management")
@Uses(Icon.class)
public class EventManagementView extends Div implements BeforeEnterObserver {

    private final String EVENTS_ID = "eventsID";
    private final String EVENTS_EDIT_ROUTE_TEMPLATE = "event-management/%s/edit";

    private Grid<Events> grid = new Grid<>(Events.class, false);

    private TextField name;
    private DatePicker startDate;
    private DatePicker endDate;
    private TimePicker startTime;
    private TimePicker endTime;
    private TextField recurring;
    private IntegerField participants;
    private TextField nonMemberPrice;
    private TextField memberPrice;
    private TextField location;
    private TextField description;
    
    private Button delete = new Button("Delete");
    private Button cancel = new Button("Cancel");
    private Button save = new Button("Save");

    private BeanValidationBinder<Events> binder;

    private Events events;

    private EventsService eventsService;

    public EventManagementView(@Autowired EventsService eventsService) {
        this.eventsService = eventsService;
        addClassNames("event-management-view", "flex", "flex-col", "h-full");

        // Create UI
        SplitLayout splitLayout = new SplitLayout();
        splitLayout.setSizeFull();

        createGridLayout(splitLayout);
        createEditorLayout(splitLayout);

        add(splitLayout);

        // Configure Grid
        grid.addColumn("name").setAutoWidth(true);
        grid.addColumn("startDate").setAutoWidth(true);
        grid.addColumn("endDate").setAutoWidth(true);
        grid.addColumn("startTime").setAutoWidth(true);
        grid.addColumn("endTime").setAutoWidth(true);
        grid.addColumn("recurring").setAutoWidth(true);
        grid.addColumn("participants").setAutoWidth(true);
        grid.addColumn("nonMemberPrice").setAutoWidth(true);
        grid.addColumn("memberPrice").setAutoWidth(true);
        grid.addColumn("location").setAutoWidth(true);
        grid.addColumn("description").setAutoWidth(true);

        grid.setItems(query -> eventsService.list(
                PageRequest.of(query.getPage(), query.getPageSize(), VaadinSpringDataHelpers.toSpringDataSort(query)))
                .stream());
        
        grid.addThemeVariants(GridVariant.LUMO_NO_BORDER);
        grid.setHeightFull();

        // when a row is selected or deselected, populate form
        grid.asSingleSelect().addValueChangeListener(event -> {
            if (event.getValue() != null) {
                UI.getCurrent().navigate(String.format(EVENTS_EDIT_ROUTE_TEMPLATE, event.getValue().getId()));
            } else {
                clearForm();
                UI.getCurrent().navigate(EventManagementView.class);
            }
        });

        // Configure Form
        binder = new BeanValidationBinder<>(Events.class);

        // Bind fields. This is where you'd define e.g. validation rules

        binder.bindInstanceFields(this);

        delete.addClickListener(e -> {
            binder.removeBean();

            eventsService.delete(this.events.getId());
            eventsService.update(this.events);
            clearForm();
            refreshGrid();
            Notification.show("Event deleted");
            UI.getCurrent().navigate(EventManagementView.class);
        });

        cancel.addClickListener(e -> {
            clearForm();
            refreshGrid();
        });

        save.addClickListener(e -> {
            try {
                if (this.events == null) {
                    this.events = new Events();
                }
                /* 
                    Since the ArrayList isn't set by the grid editor, I hardcoded an 
                    empty ArrayList to go with the new Event here
                */
                ArrayList<User> registrants = new ArrayList<User>();
                this.events.setGuests(registrants);
                binder.writeBean(this.events);

                eventsService.update(this.events);
                clearForm();
                refreshGrid();
                Notification.show("Event details saved.");
                UI.getCurrent().navigate(EventManagementView.class);
            } catch (ValidationException validationException) {
                Notification.show("An exception happened while trying to save the event details.");
            }
        });

    }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        Optional<UUID> eventsId = event.getRouteParameters().get(EVENTS_ID).map(UUID::fromString);
        if (eventsId.isPresent()) {
            Optional<Events> eventsFromBackend = eventsService.get(eventsId.get());
            if (eventsFromBackend.isPresent()) {
                populateForm(eventsFromBackend.get());
            } else {
                Notification.show(
                        String.format("The requested event was not found, ID = %s", eventsId.get()), 3000,
                        Notification.Position.BOTTOM_START);
                // when a row is selected but the data is no longer available,
                // refresh grid
                refreshGrid();
                event.forwardTo(EventManagementView.class);
            }
        }
    }

    private void createEditorLayout(SplitLayout splitLayout) {
        Div editorLayoutDiv = new Div();
        editorLayoutDiv.setClassName("flex flex-col");
        editorLayoutDiv.setWidth("400px");

        Div editorDiv = new Div();
        editorDiv.setClassName("p-l flex-grow");
        editorLayoutDiv.add(editorDiv);

        FormLayout formLayout = new FormLayout();
        name = new TextField("Event Name");
        startDate = new DatePicker("Start Date");
        endDate = new DatePicker("End Date");
        startTime = new TimePicker("Start Time");
        startTime.setStep(Duration.ofMinutes(10));
        endTime = new TimePicker("End Time");
        endTime.setStep(Duration.ofMinutes(10));
        recurring = new TextField("Recurring Event?");
        participants = new IntegerField("Number of Participants");
        nonMemberPrice = new TextField("Non-Member Price");
        memberPrice = new TextField("Member Price");
        location = new TextField("Location");
        description = new TextField("Description");
        Component[] fields = new Component[]{name, startDate, endDate, startTime, endTime, recurring, participants, nonMemberPrice, memberPrice, location, description};

        for (Component field : fields) {
            ((HasStyle) field).addClassName("full-width");
        }
        formLayout.add(fields);
        editorDiv.add(formLayout);
        createButtonLayout(editorLayoutDiv);

        splitLayout.addToSecondary(editorLayoutDiv);
    }

    private void createButtonLayout(Div editorLayoutDiv) {
        HorizontalLayout buttonLayout = new HorizontalLayout();
        buttonLayout.setClassName("w-full flex-wrap bg-contrast-5 py-s px-l");
        buttonLayout.setSpacing(true);
        cancel.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
        save.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
        delete.addThemeVariants(ButtonVariant.LUMO_ERROR);
        buttonLayout.add(save, cancel, delete);
        editorLayoutDiv.add(buttonLayout);
    }

    private void createGridLayout(SplitLayout splitLayout) {
        Div wrapper = new Div();
        wrapper.setId("grid-wrapper");
        wrapper.setWidthFull();
        splitLayout.addToPrimary(wrapper);
        wrapper.add(grid);
    }

    private void refreshGrid() {
        grid.select(null);
        grid.getLazyDataView().refreshAll();
    }

    private void clearForm() {
        populateForm(null);
    }

    private void populateForm(Events value) {
        this.events = value;
        binder.readBean(this.events);

    }
} 

The portion of code that handles event sign up for a User (If you'd prefer to see the whole class for this view instead of just this method please let me know):

private VerticalLayout createSignUpFormLayout(Dialog signUpForm) {
        signUpForm.getElement().setAttribute("aria-label", "Registration Form");

        TextField firstName = new TextField("First Name");
        TextField lastName = new TextField("Last Name");

        H2 headline = new H2("Registration Form");
        headline.getStyle().set("margin-top", "0");

        Button cancel = new Button("Cancel", e -> signUpForm.close());
        Button submit = new Button("Submit", e -> {
            if(fillChecker(firstName.getValue(), lastName.getValue()) == true) {
                String fn = firstName.getValue();
                String ln = lastName.getValue();
                User guest = new User(false, fn, ln,
                    "null", "null", Role.GUEST);

                Set<Events> selected = grid.getSelectedItems();
                Events[] curEvent = selected.toArray(new Events[1]);
                Events selectedEvent = curEvent[0];

                if(selectedEvent == null) {
                    Notification.show("No Event Selected!", 5000, Position.TOP_CENTER);
                } else {
                    userRepository.save(guest); // Saves guest sign-up info to user repository
                    selectedEvent.addRegistrant(guest);; // Adds guest sign-up info to events guest list
                    selectedEvent.decreaseCapacity(1); // Decrease events participants count by 1
                    
                    signUpForm.close();
                    Notification.show("Registered 1 Guest", 5000, Position.TOP_CENTER);
                }                
            } else {
                Notification.show("Please complete the form to register", 5000, Position.TOP_CENTER);
            }
        });

jpa

vaadin

vaadin-flow

vaadin-grid

0 Answers

Your Answer

Accepted video resources